原文

This article is based on Go 1.14. 本文基于 Go 1.14。

Go provides memory synchronization mechanisms such as channel or mutex that help to solve different issues. In the case of shared memory, mutex protects the memory against data races. However, although two mutexes exist, Go also provides atomic memory primitives via the atomic package to improve performance. Let’s first go back to the data races before diving into the solutions.
Go 提供内存同步机制,例如通道或互斥锁,这些机制有助于解决不同的问题。在共享内存的情况下,互斥锁保护内存不受数据竞争的影响。然而,尽管存在两个互斥锁,Go 也通过原子包提供原子内存原语来提高性能。在深入研究解决方案之前,让我们先回顾一下数据竞赛。

Data Race(数据竞赛)

A data race can occur when two or more goroutines access the same memory location concurrently, and at least one of them is writing. While the maps have a native mechanism to protect against data races, a simple structure does not have any, making it vulnerable to data races.
当两个或多个 goroutine 并发访问相同的内存位置并且其中至少有一个正在写入时,可能会发生数据竞赛。虽然 map 有一个本地机制来防止数据竞赛,但是结构体没有任何防止数据竞赛的手段,这使得它容易受到数据竞赛的影响。
To illustrate a data race, I will take an example of a configuration that is continuously updated by a goroutine. Here is the code:
为了说明数据竞赛,我将举一个配置的例子,该配置由 goroutine 不断更新。下面是代码:

  1. type Config struct {
  2. a []int
  3. }
  4. func main() {
  5. cfg := &Config{}
  6. go func() {
  7. i := 0
  8. for {
  9. i++
  10. cfg.a = []int{i, i+1, i+2, i+3, i+4, i+5}
  11. }
  12. }()
  13. var wg sync.WaitGroup
  14. for n:=0; n<4; n++ {
  15. wg.Add(1)
  16. go func() {
  17. for n:=0; n<100; n++ {
  18. fmt.Printf("#{cfg}\n")
  19. }
  20. wg.Done()
  21. }()
  22. }
  23. wg.Wait()
  24. }

Running this code clearly shows that the result is non-deterministic due to the data race:
运行这段代码可以清楚地看到,由于数据竞争,结果是不确定的:

  1. [...]
  2. &{[79167 79170 79173 79176 79179 79181]}
  3. &{[79216 79219 79220 79221 79222 79223]}
  4. &{[79265 79268 79271 79274 79278 79281]}

Each line was expecting to be a continuous sequence of integers when the result is quite random. Running the same program with the flag -race points out the data races:
每一行的期望是一个连续的整数序列, 尽管结果是相当随机的。用 flag -race 运行相同的程序可以指出数据竞赛:

  1. WARNING: DATA RACE
  2. Read at 0x00c0003aa028 by goroutine 9:
  3. [...]
  4. fmt.Printf()
  5. /usr/local/go/src/fmt/print.go:213 +0xb5
  6. main.main.func2()
  7. main.go:30 +0x3b
  8. Previous write at 0x00c0003aa028 by goroutine 7:
  9. main.main.func1()
  10. main.go:20 +0xfe

Protecting our reads and writes from data races can be done by a mutex — probably the most common one — or by the atomic package.
保护我们的读写不受数据竞赛的影响可以通过 Mutex (可能是最常见的一种)或 atomic 包来完成。

Mutex vs Atomic

The standard library provides two kinds of mutex with the sync package: sync.Mutex and sync.RWMutex; the latter is optimized when your program deals with multiples readers and very few writers. Here is one solution:
标准库 sync 包提供了两种互斥锁:sync.Mutexsync.RWMutex;当您的程序处理多个读和很少的写时,后者会得到优化。 这是一种解决方案:

  1. func main() {
  2. cfg := &Config{}
  3. go func() {
  4. var i int
  5. for {
  6. i++
  7. lock.Lock()
  8. cfg.a = []int{i, i+1, i+2, i+3, i+4, i+5}
  9. lock.Unlock()
  10. }
  11. }()
  12. var wg sync.WaitGroup
  13. for n:=0; n<4; n++ {
  14. wg.Add(1)
  15. go func() {
  16. for n:=0; n<100; n++ {
  17. lock.RLock()
  18. fmt.Print("#{cfg}\n")
  19. lock.RUnlock()
  20. }
  21. wg.Done()
  22. }()
  23. }
  24. wg.Wait()
  25. }

The program now prints out the expected result; the numbers are properly incremented:
程序现在打印出预期的结果;数字正确递增:

  1. [...]
  2. &{[213 214 215 216 217 218]}
  3. &{[214 215 216 217 218 219]}
  4. &{[215 216 217 218 219 220]}

The second solution can be done thanks to the atomic package. Here is the code:
第二种解决方式可以使用 atomic 包。这是代码:

  1. func main() {
  2. var v atomic.Value
  3. go func() {
  4. var i int
  5. for {
  6. i++
  7. cfg := &Config {
  8. a: []int{i, i+1, i+2, i+3, i+4, i+5}
  9. }
  10. v.Store(cfg)
  11. }
  12. }()
  13. var wg sync.WaitGroup
  14. for n:=0; n<4; n++ {
  15. wg.Add(1)
  16. go func() {
  17. for n:=0; n<100; n++ {
  18. cfg := v.Load()
  19. fmt.Print("#{cfg}\n")
  20. }
  21. wg.Done()
  22. }()
  23. }
  24. wg.Wait()
  25. }

也是预期的结果:

  1. [...]
  2. &{[32724 32725 32726 32727 32728 32729]}
  3. &{[32733 32734 32735 32736 32737 32738]}
  4. &{[32753 32754 32755 32756 32757 32758]}

Regarding the generated output, it looks like the solution using the atomic package is much faster since it can generate a higher sequence of numbers. Benchmarking both of the programs would help to figure out which one is the most efficient.
关于生成的输出,看起来使用 atomic 包的解决方案要快得多,因为它可以生成更高的数字序列。 对这两个程序进行基准测试将有助于找出哪个程序最有效。

PS:以下就不翻译了,关于基准测试的