在线文档  >   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 个操作。
对于这种特殊情况,基于 协程 的方法比基于互斥锁的方法更复杂一些。但是,在某些情况下,它可能很有用,例如,当您涉及其他通道或管理多个此类互斥锁时容易出错时。您应该使用任何感觉最自然的方法,尤其是在理解程序的正确性方面。