[Go] -race 参数的使用及原理

1,348 阅读2分钟

-race 是 Go 的一个编译参数,用来检测并发程序中的数据竞争问题(竞态检测)。

一、原理

数据竞争指的是多个 goroutine 在没有明确同步的情况下访问共享变量,可能导致该变量的值变得不确定或产生其它意外行为。这种问题通常很难被发现和修复,因为它们可能在一些情况下能正常工作,但在其它情况下失败。

-race 的实现原理是通过在编译时对程序进行修改,向每个内存访问操作添加一个信号量。如果在运行时某个变量被多个 goroutine 访问,并且它们中有至少一个 goroutine 进行了写入,那么就会触发一个信号量,程序会中断并打印有关数据竞争的详细信息。这些信息包括哪些 goroutine 在哪些行上访问了哪些共享变量。

二、例子

1.main.go

// main.go
package main

import (
	"fmt"
	"sync"
)

var counter int

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter++
		}()
	}
	wg.Wait()
	fmt.Println("Counter:", counter)
}

main.go定义了一个 counter 变量,并在主函数中启动了 100 个 goroutine,每个 goroutine 都会将 counter 值增加 1。由于这些 goroutine 之间没有同步机制,可能出现数据竞争问题。

2.命令行

$ go run -race main.go
==================
WARNING: DATA RACE
Read at 0x00c00009e008 by goroutine 8:
  main.main.func1()
      /path/to/main.go:15 +0x48

Previous write at 0x00c00009e008 by goroutine 6:
  main.main.func1()
      /path/to/main.go:15 +0x60

Goroutine 8 (finished) created at:
  main.main()
      /path/to/main.go:12 +0x7e

Goroutine 6 (running) created at:
  main.main()
      /path/to/main.go:12 +0x7e
==================
Counter: 100
Found 1 data race(s)
exit status 66

输出结果中显示了一个数据竞争警告。通过提示,可以看出第 8 个 goroutine 在读取 counter 变量时,同时第 6 个 goroutine 正在写入该变量,这可能导致变量值变得不确定或出现其它问题。

3.解决方案:在关键代码前后加锁

package main

import (
	"fmt"
	"sync"
)

var counter int
var mu sync.Mutex

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			counter++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("Counter:", counter)
}

使用 Mutex 可以保证每个 goroutine 在修改 counter 变量时都会先获取锁,这样就可以避免数据竞争。重新编译并运行程序,不再出现数据竞争的警告。

三、参考文献

Introducing the Go Race Detector - The Go Programming Language