Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

stengaard commented Mar 13, 2014

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

This comment has been minimized.

Copy link
Owner Author

deckarep commented Mar 24, 2014

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

This comment has been minimized.

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
You can’t perform that action at this time.