Skip to content

Instantly share code, notes, and snippets.

@olekukonko
Forked from deckarep/goroutine-channel-pattern.go
Created March 12, 2014 23:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save olekukonko/9518637 to your computer and use it in GitHub Desktop.
Save olekukonko/9518637 to your computer and use it in GitHub Desktop.
type Account struct {
balance float64
deltaChan chan float64
balanceChan chan float64
errChan chan error
}
func NewAccount() (a *Account) {
a = &Account{
balance: balance,
deltaChan: make(chan float64),
balanceChan: make(chan float64),
errChan: make(chan error),
}
go a.run()
}
func (a *Account) Balance() float64 {
return <-a.balanceChan
}
func (a *Account) Deposit(amount float64) error {
a.deltaChan <- amount
return <-a.errChan
}
func (a *Account) Withdraw(amount float64) error {
a.deltaChan <- -amount
return <-a.errChan
}
func (a *Account) applyDelta(amount float64) error {
newBalance := a.balance + amount
if newBalance < 0 {
return errors.New("Insufficient funds")
}
a.balance = newBalance
return nil
}
func (a *Account) run() {
var delta float64
for {
select {
case delta = <-a.deltaChan:
a.errChan <- a.applyDelta(delta)
case a.balanceChan <- a.balance:
// Do nothing, we've accomplished our goal w/ the channel put.
}
}
}
/*
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
}
type Db struct {
db map[string]string
storeChannel chan KeyValue
fetchChannel chan KeyValue
f func()
}
func NewDb() *Db {
d := &Db{}
d.db = make(map[string]string)
d.storeChannel = make(chan KeyValue)
d.fetchChannel = make(chan KeyValue)
d.f = func() {
for {
select {
case storeValue := <-d.storeChannel:
d.internalStore(storeValue)
case fetchKey := <-d.fetchChannel:
d.fetchChannel <- d.internalFetch(fetchKey)
}
}
}
go d.f()
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 {
d.fetchChannel <- KeyValue{Key: key}
result := <-d.fetchChannel
return result
}
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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment