Last active
December 13, 2016 20:46
-
-
Save deckarep/7685352 to your computer and use it in GitHub Desktop.
A Goroutine safe pattern using channels that abstracts away the usage of channels.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
A Goroutine safe pattern using channels that abstracts away the channels | |
This is a concept of creating a goroutine safe object that uses channels under the covers to communicate | |
with the internal map[string]string structure. I know that typically this kind of solution may done | |
with mutexes but the excercise was in using channels on purpose although they are heavier. | |
Note a couple of points: | |
- When using channels, you can still build a public-facing api that nicely abstracts them away, therefore | |
somemone using this api for example, doesn't have to understand the paradigm of communicating over channels | |
- This example is just a prototype, as an example | |
- Notice that all state is mutated internal to the Db's f function | |
- Notice that in the Fetch method there is bi-directional communication a send/receive | |
*/ | |
package main | |
import "fmt" | |
import "time" | |
type KeyValue struct { | |
Key string | |
Value string | |
Reply chan KeyValue | |
} | |
type Db struct { | |
db map[string]string | |
storeChannel chan KeyValue | |
fetchChannel chan KeyValue | |
} | |
func NewDb() *Db { | |
d := &Db{} | |
d.db = make(map[string]string) | |
d.storeChannel = make(chan KeyValue) | |
d.fetchChannel = make(chan KeyValue) | |
go func() { | |
for { | |
select { | |
case storeValue := <-d.storeChannel: | |
d.internalStore(storeValue) | |
case fetchKey := <-d.fetchChannel: | |
fetchKey.Reply <- d.internalFetch(fetchKey) | |
} | |
} | |
}() | |
return d | |
} | |
func (d *Db) internalFetch(kv KeyValue) KeyValue { | |
v, ok := d.db[kv.Key] | |
if ok { | |
return KeyValue{Key: kv.Key, Value: v} | |
} | |
return KeyValue{Key: kv.Key} | |
} | |
func (d *Db) internalStore(kv KeyValue) { | |
d.db[kv.Key] = kv.Value | |
fmt.Println("Just stored: ", kv) | |
} | |
func (d *Db) Fetch(key string) KeyValue { | |
ch := make(chan KeyValue) | |
d.fetchChannel <- KeyValue{Key: key, Reply: ch} | |
return <-ch | |
} | |
func (d *Db) Store(key string, value string) { | |
d.storeChannel <- KeyValue{Key: key, Value: value} | |
} | |
func main() { | |
myDb := NewDb() | |
//myDb can safely be used by many goroutines although in this example it's only used by the main goroutine. | |
myDb.Store("id-3383", "John") | |
myDb.Store("id-2218", "Ralph") | |
myDb.Store("id-7741", "Amy") | |
//simulate some time has gone by | |
time.Sleep(time.Second * 1) | |
fmt.Println(myDb.Fetch("id-3383")) | |
fmt.Println(myDb.Fetch("id-7741")) | |
//not found returns a KeyValue with an empty value | |
fmt.Println(myDb.Fetch("id-9965")) | |
var s string | |
fmt.Scanln(&s) | |
} |
You are exactly right on this...I have updated the code. Thanks for the catch.
Note: I'm not sure if you would classify this issue I had as racy, but it the order of execution was definitely off.
You could use a chan chan
:-)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Isn't this racy?
In the Fetch method. If two go routines (a and b) enter it with keys "A" and "B", go routine a may send on the channel first, leaving b to be blocked on the send. a is now blocked on receive from fetchChannel. a will simply receive b's KeyValue struct.
To get around you would need to include a reply-chan in the type flowing on fetchChannel, right? Is there a better way?
/Brian