-
-
Save olekukonko/9518637 to your computer and use it in GitHub Desktop.
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
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. | |
} | |
} | |
} |
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 | |
} | |
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