Skip to content

Instantly share code, notes, and snippets.

@axgle
Forked from deckarep/goroutine-channel-pattern.go
Last active August 29, 2015 13:58
Show Gist options
  • Save axgle/10229301 to your computer and use it in GitHub Desktop.
Save axgle/10229301 to your computer and use it in GitHub Desktop.
/*
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
*/
// other detail info: https://blog.mozilla.org/services/2014/03/12/sane-concurrency-with-go/
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)
}
@axgle
Copy link
Author

axgle commented Apr 9, 2014

package main

import (
    "fmt"

    "time"
)

type Db struct {
    db        []int
    storeChan chan int
    replyChan chan chan int
}

func NewDb() *Db {
    d := &Db{storeChan: make(chan int), replyChan: make(chan chan int)}
    go func() {
        for {
            select {
            case k := <-d.storeChan:
                d.db = append(d.db, k)

            case reply := <-d.replyChan:

                var head = -1
                if len(d.db) > 0 {
                    head = d.db[0]
                    d.db = d.db[1:]
                }

                reply <- head

            }
        }
    }()
    return d
}
func (d *Db) Store(k int) {
    d.storeChan <- k

}
func (d *Db) Fetch() int {

    reply := make(chan int)
    d.replyChan <- reply
    return <-reply
}
func main() {
    db := NewDb()
    go db.Store(1)
    go db.Store(2)
    go db.Store(3)
    go db.Store(4)

    time.Sleep(time.Millisecond) // wait for save data

    for i := 0; i < 4; i++ {
        fmt.Printf("%d", db.Fetch())
    }
    fmt.Printf("%d", db.Fetch())

    var s string
    fmt.Scanln(&s)

}

@axgle
Copy link
Author

axgle commented Apr 9, 2014

package main

import (
    "fmt"
)

type Counter struct {
    cnt        int //shared state
    delta      chan int
    replyChan  chan chan int
    replyChan2 chan int
}

func NewCounter() *Counter {
    c := &Counter{
        delta:      make(chan int),
        replyChan:  make(chan chan int),
        replyChan2: make(chan int),
    }
    go func() {
        for {
            select {
            case n := <-c.delta:
                c.cnt += n
            case reply := <-c.replyChan:
                reply <- c.cnt //after signaled,can do more
            case c.replyChan2 <- c.cnt: //after signaled,directly return
            }

        }
    }()
    return c
}
func (c *Counter) Add(n int) {
    c.delta <- n
}
func (c *Counter) C2() int {
    return <-c.replyChan2
}
func (c *Counter) C() int {
    reply := make(chan int)
    c.replyChan <- reply
    return <-reply
}
func main() {
    echo := fmt.Println

    c := NewCounter()

    c.Add(2)
    echo(c.C())
    echo(c.C2())
    c.Add(1)
    echo(c.C())
    echo(c.C2())
    c.Add(-13)
    echo(c.C())
    echo(c.C2())
}

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