116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
// In the previous example we used explicit locking with
|
|
// [mutexes](mutexes) to synchronize access to shared state
|
|
// across multiple goroutines. Another option is to use the
|
|
// built-in synchronization features of goroutines and
|
|
// channels to achieve the same result. This channel-based
|
|
// approach aligns with Go's ideas of sharing memory by
|
|
// communicating and having each piece of data owned
|
|
// by exactly 1 goroutine.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// In this example our 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` `struct`s
|
|
// 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() {
|
|
|
|
// As before we'll count how many operations we perform.
|
|
var readOps uint64
|
|
var writeOps uint64
|
|
|
|
// The `reads` and `writes` channels will be used by
|
|
// other goroutines to issue read and write requests,
|
|
// respectively.
|
|
reads := make(chan readOp)
|
|
writes := make(chan writeOp)
|
|
|
|
// Here is the 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. A response
|
|
// is executed by first performing the requested
|
|
// operation and then sending a value on the response
|
|
// channel `resp` to indicate success (and the desired
|
|
// value in the case of `reads`).
|
|
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
|
|
}
|
|
}
|
|
}()
|
|
|
|
// This starts 100 goroutines to issue reads to the
|
|
// state-owning goroutine via the `reads` channel.
|
|
// Each read requires constructing a `readOp`, sending
|
|
// it over the `reads` channel, and the receiving the
|
|
// result over the provided `resp` channel.
|
|
for r := 0; r < 100; r++ {
|
|
go func() {
|
|
for {
|
|
read := readOp{
|
|
key: rand.Intn(5),
|
|
resp: make(chan int)}
|
|
reads <- read
|
|
<-read.resp
|
|
atomic.AddUint64(&readOps, 1)
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// We 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)
|
|
|
|
// Finally, capture and report the op counts.
|
|
readOpsFinal := atomic.LoadUint64(&readOps)
|
|
fmt.Println("readOps:", readOpsFinal)
|
|
writeOpsFinal := atomic.LoadUint64(&writeOps)
|
|
fmt.Println("writeOps:", writeOpsFinal)
|
|
}
|