在线文档 > Golang练习 > 协程状态管理
在之前的示例中,我们使用显式锁定和互斥量来同步多个 协程 之间对共享状态的访问。
另一个选项是使用 协程 和通道的内置同步特性来实现相同的结果。这种基于通道的方法与 Go 的通过通信共享内存的理念相一致,使每个数据都归属于一个 协程。
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// 在这个示例中,我们的 state 将由单个 协程 拥有,这样可以确保数据在并发访问时不会被损坏。
// 为了对 state 读取或写入,其它的协程将发送一条数据到目前拥有数据的协程中, 然后等待接收对应的回复。
// 结构体 readOp 和 writeOp 封装了这些请求,并提供了响应协程的方法。
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
func main() {
// 与之前一样,我们将计算我们执行了多少操作。
var readOps uint64
var writeOps uint64
// `reads` 和 `writes` 通道将被其他 协程 分别用于发出读和写请求。
reads := make(chan readOp)
writes := make(chan writeOp)
// 这里是拥有 `state` 的 协程,它是一个类似于上一个示例中的 map,但现在是私有的。
// 这个 协程 反复地选择 `reads` 和 `writes` 通道,响应请求。
// 响应首先执行所请求的操作,然后通过响应通道 `resp` 发送一个值来表示成功(在读取情况下是期望的值)。
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
}
}
}()
// 这将启动100个 协程 通过 `reads` 通道向拥有 state 的 协程 发出读取请求。
// 每次读取都需要构造一个 `readOp`,将其发送到 `reads` 通道中,然后通过提供的 `resp` 通道接收结果。
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)
}
}()
}
// 我们也会开10个写入操作,使用类似的方法。
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)
}
}()
}
// 让 协程 工作一秒钟。
time.Sleep(time.Second)
// 最后,捕获并报告操作计数。
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
运行结果如下:
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177
运行我们的程序表明,基于 协程 的状态管理示例总共完成了大约 80000 个操作。
对于这种特殊情况,基于 协程 的方法比基于互斥锁的方法更复杂一些。但是,在某些情况下,它可能很有用,例如,当您涉及其他通道或管理多个此类互斥锁时容易出错时。您应该使用任何感觉最自然的方法,尤其是在理解程序的正确性方面。