go_playground/5-go-by-example/40-stateful-goroutines.go

91 lines
2.3 KiB
Go

// built-in synchronization features of goroutines and channels
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// state will be owned by a single goroutine
// this will guarantee that the data is never corrupted with concurrent access
// in order to read or write that state, other goroutines will send messages to the owning goroutine and receive corresponding replies
// these readOp and writeOp structs encapsulate those requests and a way for the owning goroutine to respond
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main() {
// count how many operations to perform
var readOps uint64
var writeOps uint64
// reads and writes channels will be used by other goroutines
reads := make(chan readOp)
writes := make(chan writeOp)
// goroutine that owns the state, which is a map as in the previous example but now private to the stateful goroutine
// this goroutine repeatedly selects on the reads and writes channels, responding to requests as they arrive
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
// starts 100 goroutines to issue reads to the state-owning goroutine via the reads channel
for r := 0; r < 100; r++ {
go func() {
for {
// each read requires constructing a readOp, sending it over the reads channel, and the receiving the result over the provided resp channel
read := readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// start 10 writes as well, using a similar approach
for w := 0; w < 10; w++ {
go func() {
for {
write := writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
// let the goroutines work for a second
time.Sleep(time.Second)
// capture and report the op counts
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}