Skip to content

Instantly share code, notes, and snippets.

@deckarep
Last active December 13, 2016 20:46
Show Gist options
  • Save deckarep/7685352 to your computer and use it in GitHub Desktop.
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.
/*
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)
}
@stengaard
Copy link

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

@deckarep
Copy link
Author

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.

@sprt
Copy link

sprt commented Dec 13, 2016

You could use a chan chan :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment