在前一个示例中,我们使用了显式锁定和互斥锁来同步多个 goroutines 之间对共享状态的访问。另一个选择是使用 goroutines 和通道的内建同步功能来实现相同的结果。这种基于通道的方法与 Go 的理念一致,即通过通信来共享内存,并且每个数据片段只能由一个 goroutine 拥有。
package main
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
// 在这个例子中,我们的状态将由单个 goroutine 拥有。这将保证数据在并发访问时不会被损坏。为了读取或写入这个状态,其他 goroutine 将向拥有状态的 goroutine 发送消息,并接收相应的回复。这些 readOp 和 writeOp 结构体封装了这些请求,以及拥有 goroutine 响应请求的方式。
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
// 读取和写入通道将被其他 goroutine 用来分别发出读取和写入请求。
reads := make(chan readOp)
writes := make(chan writeOp)
// 这是拥有状态的 goroutine,状态是一个像前面例子中的 map,但现在是该有状态 goroutine 私有的。这个 goroutine 会重复地在读取和写入通道上进行选择,响应到达的请求。响应的执行过程是,首先执行请求的操作,然后通过响应通道 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 个 goroutine,通过读取通道向拥有状态的 goroutine 发出读取请求。每个读取操作需要构造一个 readOp,将其发送到读取通道,然后通过提供的 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)
}
}()
}
// 让 goroutines 工作一秒钟。
time.Sleep(time.Second)
// 最后,捕获并报告操作计数。
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
运行我们的程序显示,基于 goroutine 的状态管理示例总共完成了大约 90,000 次操作。
➜ go run stateful-goroutines.go
readOps: 81924
writeOps: 8248
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。