> For the complete documentation index, see [llms.txt](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wskdsgcf.gitbook.io/mastering-go-zh-cn/10.0/10.6.md).

# 10.6 竞争状态

**数据竞争状态** 是两个多个运行中的如线程和 goroutines 试图控制或修改共享的资源或程序变量。严格讲，数据竞争发生在当两个或多个指令访问同一个内存地址，它们中至少有一个在此执行写操作。

当运行或构建 Go 源文件时使用 `-race` 标志会开启 Go **竞争监视器**，它会使编译器创建一个典型的可执行文件的修改版。这个修改版可以记录所有对共享变量的访问以及发生的同步事件，包括调用 `sync.Mutex` 和 `sync.WaitGroup`。分析相关事件后，竞争监视器打印一份报告来帮助您识别潜在问题，这样您就可以修正它们了。

请看下面的 Go 代码，它保存为 `racec.go`。这个程序分三部分来介绍。

`raceC.go` 的第一部分如下：

```go
package main

import (
    "fmt"
    "os"
    "strconv"
    "sync"
)

func main() {
    arguments := os.Args
    if len(arguments) != 2 {
        fmt.Println("Give me a natural number!")
        os.Exit(1)
    }
    numberGR, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }
```

`raceC.go` 的第二段代码如下：

```go
    var waitGroup sync.WaitGroup
    var i int

    k := make(map[int]int)
    k[1] = 12
    for i = 0; i < numGR; i++ {
        waitGroup.Add(1)
        go func() {
            defer waitGroup.Done()
            k[i] = i
        }()
    }
```

`raceC.go` 的其余代码如下：

```go
    k[2] = 10
    waitGroup.Wait()
    fmt.Println("k = %v\n", k)
}
```

好像许多 goroutines 同时访问 `k` map 还不够，我们在调用 `sync.Wait()` 函数前再添加一个访问 `k` map 的语句。

如果您执行 `raceC.go`，会获得如下没有任何警告或错误信息的输出：

```
$go run raceC.go 10
k = map[int]int{7:10, 2:10, 10:10, 1:12}
$go run raceC.go 10
k = map[int]int{2:10, 10:10, 1:12, 8:8, 9:9}
$go run raceC.go 19
k = map[int]int{10:10, 1:12, 6:7, 7:7, 2:10}
```

如果您仅执行一次 `raceC.go`，当打印 `k` map 内容的时候尽管您没有得到期望的，但一切看起来正常。然而，多次执行 `raceC.go` 告诉我们这有些错误，主要是每次执行产生不同的输出。

如果我们决定使用 Go 竞争监视器分析 `raceC.go`，我们可以获得更多信息及意外输出：

![""](https://github.com/hantmac/Mastering_Go_ZH_CN/tree/master/images/chapter10/10.6-1.jpg)

竞争监视器发现两处数据竞争。每个都在它的输出里用 `WARNING: DATA RACE` 消息开头。

第一个 **数据竞争** 发生在 `main.main.func1()` 内，它由一个goroutine 执行的 `for` 循环调用。这的问题由 `Previous write` 消息表示。检查相关代码后，很容易看到实际问题是匿名函数没带参数，意思是在 `for` 循环中使用的 `i` 值不同准确识别，因为这是个写操作，在 `for` 循环内不断改变。

第二处数据竞争信息是 `Write at 0x00c420074180 by goroutine 7`。如果您阅读相关输出，会看到这个数据竞争是关于写操作的，并且至少有两个 goroutines 在执行。因为这两个 goroutine 有相同的名字（`main.main.func1()`），这表明我们谈论的是同一个 goroutine。这两个 goroutine 试图写同一个变量，这就是数据竞争状态！

> *Go 用* `main.main.func1()` *记号命名一个内部地匿名函数。如果您有不同地匿名函数，它们的名字同样会不同*

您可以问之际，现在为了修正这两个数居竞争引起的问题我能做什么？

好的，您可以重写 `raceC.go` 的 `main()` 函数如下：

```go
func main() {
    arguments := os.Args
    if len(arguments) != 2 {
        fmt.Println("Give me a natural number!")
        os.Exit(1)
    }
    numGR, err := srconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println(err)
        return
    }

    var waitGroup syncWaitGroup
    var i int

    k := make(map[int]int)
    k[1] = 12

    for i = 0; i < numGR; i++ {
        waitGroup.Add(1)
        go runc(j int) {
            defer waitGroup.Done()
            aMutex.Lock()
            k[j] = j
            aMutex.Unlock()
        }(i)
    }

    waitGroup.Wait()
    k[2] = 10
    fmt.Printf("k = %#v\n", k)
}
```

`aMutex` 变量是一个定义在 `main()` 函数外的全局 `sync.Mutex` 变量，它可以在程序的任何地方访问到。尽管这不必要，不过有这样一个全局变量可以免得您总是把它传给函数。

把这个新版本 `raceC.go` 保存为 `noRaceC.go` 并执行它产生如下输出：

```
$go run noRaceC.go 10
k = map[int]int{1:1, 0:0, 5:5, 3:3, 6:6, 9:9, 2:10, 4:4, 7:7, 8:8}
```

用 Go 竞争监测器运行 `noRaceC.go` 产生如下输出：

```
$go run -race noRaceC.go 10
k = map[int]int{5:5, 7:7, 9:9, 1:1, 0:0, 4:4, 6:6, 8:8, 2:10, 3:3}
```

注意当访问 `k` map 时，您需要一个锁机制。如果您没有使用这样的机制并且只修改了由 goroutine 执行的匿名函数的实现的话，您会从 `go run noRaceC.go` 获得如下输出：

![""](https://github.com/hantmac/Mastering_Go_ZH_CN/tree/master/images/chapter10/10.6-2.jpg)

这个根本问题是显而易见的：`并发 map 写操作`。
