Go 语言并发中的原子计数器

Beginner

This tutorial is from open-source community. Access the source code

简介

本实验重点介绍如何在 Go 语言中使用sync/atomic包来管理状态,该包用于处理由多个 goroutine 访问的原子计数器。

原子计数器

问题是使用 50 个 goroutine 和sync/atomic包将一个计数器精确递增 1000 次。

  • 使用sync/atomic包来递增计数器。
  • 使用 WaitGroup 等待所有 goroutine 完成它们的工作。
## 我们期望得到恰好 50000 次操作。如果我们使用非原子的 `ops++` 来递增计数器,
## 我们可能会得到不同的数字,并且每次运行时都会变化,因为 goroutine 会相互干扰。
## 此外,当使用 `-race` 标志运行时,我们会得到数据竞争失败。
$ go run atomic-counters.go
ops: 50000

## 接下来我们将看看互斥锁,这是另一种管理状态的工具。

以下是完整代码:

// 在 Go 语言中管理状态的主要机制是通过通道进行通信。例如,我们在 [工作池](worker-pools) 中看到了这一点。不过,还有其他一些管理状态的选项。在这里,我们将看看如何使用 `sync/atomic` 包来处理由多个 goroutine 访问的_原子计数器_。

package main

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

func main() {

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

	// 一个 WaitGroup 将帮助我们等待所有 goroutine 完成它们的工作。
	var wg sync.WaitGroup

	// 我们将启动 50 个 goroutine,每个 goroutine 将计数器精确递增 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()
		}()
	}

	// 等待直到所有 goroutine 完成。
	wg.Wait()

	// 现在访问 `ops` 是安全的,因为我们知道没有其他 goroutine 正在写入它。在原子变量更新时安全地读取它也是可能的,使用像 `atomic.LoadUint64` 这样的函数。
	fmt.Println("ops:", ops)
}

总结

在本实验中,我们学习了如何在 Go 语言中使用sync/atomic包来管理状态,即通过多个 goroutine 递增一个计数器。我们使用AddUint64函数来原子地递增计数器,并使用 WaitGroup 来等待所有 goroutine 完成它们的工作。