Skip to content

Latest commit

 

History

History
103 lines (71 loc) · 4.78 KB

11-2-Not-enabling-the-race-flag.md

File metadata and controls

103 lines (71 loc) · 4.78 KB

11.2 启用 race 标志检查是否存在数据竞争

不理解竞争问题 中,我们定义了一个数据竞争,即两个 goroutine 同时访问同一个变量,并且至少有一个写入该变量。除此之外,我们应该知道,在 Go 中,存在一个标准工具来帮助检测数据竞争。一个常见的错误是忘记了这个工具的重要性并且没有启用它。本节将深入研究竞争检测器捕获的内容、如何使用它以及限制。

在 Go 中,竞争检测器不是编译期间会发生的静态分析工具;相反,它是一种查找运行时发生的数据竞争的工具。

要启用它,我们必须在编译或运行测试时设置 ‑race 命令行,例如:

$ go test -race ./...

启用后,编译器将检测代码以检测数据竞争。Instrumentation 是指编译器添加额外的指令。在这里,跟踪所有内存访问并记录它们发生的时间和方式。然后,在运行时,竞争检测器将监视数据竞争。但是,我们应该记住启用竞争检测器的运行时开销:

启用后,编译器将检测代码以检测数据竞争。检测是指编译器添加额外的指令。在这里,跟踪所有内存访问并记录它们发生的时间和方式。然后,在运行时,竞争检测器将监视数据竞争。但是,我们应该记住启用竞争检测器的运行时开销:

  • 内存使用量可能会增加 5‑10 倍
  • 执行时间可能会增加 2‑20 倍

由于这种开销,通常建议仅在本地测试或 CI 期间启用竞争检测器。在生产环境中,我们应该避免它,或者只在金丝雀(少量用户使用)版本的情况下使用它。

但是,我们应该记住,无论执行上下文如何,Go 竞争检测器对同时执行的 goroutine 的数量都有一个严格的限制:8128。超过这个阈值,竞争检测器将停止。

如果检测到竞争,Go 将发出警告。例如,此示例包含数据竞争,因为可以同时访问 i 以进行读取和写入:

package main

import (
    "fmt"
)

func main() {
	i := 0
	go func() { i++ }()
	fmt.Println(i)
}

使用 ‑race 标志运行此应用程序将记录以下数据争用警告:

==================
WARNING: DATA RACE
Write at 0x00c000026078 by goroutine 7:
  main.main.func1()
      /tmp/app/main.go:9 +0x4e

Previous read at 0x00c000026078 by main goroutine:
  main.main()
      /tmp/app/main.go:10 +0x88

Goroutine 7 (running) created at:
  main.main()
      /tmp/app/main.go:9 +0x7a
==================

让我们确保我们在阅读这些信息时感到自在。Go 总是记录:

  • 并发 goroutine 有哪些:主 goroutine 和 goroutine 7
  • 访问发生在代码中的位置:第 9 行和第 10 行
  • 这些 goroutines 是什么时候创建的:在 main() 中创建了goroutine 7

Note 在内部,竞争检测器使用矢量时钟,这是一种用于确定事件的部分顺序的数据结构(也用于数据库等分布式系统)。每个 goroutine 创建都会导致创建一个向量时钟。然后,仪器在每次内存访问和同步事件时更新矢量时钟。然后,它比较矢量时钟以检测潜在的数据竞争。

我们应该注意,竞争检测器无法捕获误报(不是真实的数据竞争)。因此,如果我们收到警告,我们可以知道我们的代码包含数据竞争。

相反,它在某些情况下可能会导致误报:缺少实际的数据竞争。测试方面需要注意两件事。首先,竞争检测器只能和我们的测试一样好。因此,我们应该确保针对数据竞争对并发代码进行彻底测试。此外,考虑到可能的假阴性,如果我们确实有一个测试来检查数据竞争,一个选择可能是将这个逻辑放在一个循环中。这样,我们可以增加捕获可能的数据竞争的机会:

func TestDataRace(t *testing.T) {
    for i := 0; i < 100; i++ {
        // Actual logic
    }
}

关于测试的最后一件事。如果特定文件包含导致数据竞争的测试,我们可以使用 !race 构建标签将其从竞争检测中排除:

//go:build !race

package main

import (
    "testing"
)

func TestFoo(t *testing.T) {
    // ...
}

func TestBar(t *testing.T) {
    // ...
}

仅当禁用竞争检测器时才会构建此文件。否则,将不会构建整个文件;因此,不会执行测试。

总之,我们应该记住,如果不是强制性的,强烈建议使用 ‑race 标志为利用并发的应用程序运行测试。它允许启用竞争检测器,该检测器检测我们的代码以捕获潜在的数据竞争。启用后,它会对内存和性能产生重要影响;因此它必须在特定条件下使用,例如本地测试或 CI。

下一节将讨论与执行模式相关的两个标志:parallelshuffle