# 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 写操作`。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://wskdsgcf.gitbook.io/mastering-go-zh-cn/10.0/10.6.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
