在线文档  >   Golang练习   >   原子计数器

在 Go 中,管理状态的主要机制是通过通道进行通信。我们在工作池中看到过这个例子。
然而,还有其他一些选项来管理状态。这里,我们将看看如何使用sync/atomic包处理多个协程访问的原子计数器。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {

    // 我们将使用无符号整数来表示我们的(始终为正的)计数器。
    var ops uint64

    // WaitGroup帮助我们等待所有协程完成它们的工作。
    var wg sync.WaitGroup

    // 我们将启动50个协程,每个协程精确地增加计数器到1000次。
    for i := 0; i < 50; i++ {
        wg.Add(1)

        go func() {
            for c := 0; c < 1000; c++ {

                // 要原子性地增加计数器,我们使用`AddUint64`,并使用“&”语法获取我们的“ops”计数器的内存地址。
                atomic.AddUint64(&ops, 1)
            }
            wg.Done()
        }()
    }

    // 等待直到所有的协程完成。
    wg.Wait()

    // 由于我们知道没有其他 协程 正在写入它,因此现在可以安全地访问`ops`。像使用`atomic.LoadUint64`这样的函数,在原子更新的同时安全地读取它们。
    fmt.Println("ops:", ops)
}

运行结果如下:

$ go run atomic-counters.go
ops: 50000

我们预计正好得到 50000 次操作。如果我们使用非原子的ops++来增加计数器,由于多个协程会互相干扰,运行时值会改变,可能会导致我们得到一个不同的数字。 此外,运行程序时带上 -race 标志,我们可以获取数据竞争失败的详情。

接下来,我们看一下管理状态的另一个工具——互斥锁。