2.竞态检测器

278 阅读3分钟

1.概述

字段类型含义
raceenabledbool定义在runtime包中。它用于启用数据竞争检查功能,让编译器和运行时监视器能检测到并报告潜在的危险行为

2.函数

2.1 racereadpc

调用此函数的函数指针,用于定位当前函数在代码中的位置。

FuncPCABIInternal,用于获取chansend函数的指针,表示这个函数的PC值是在Go函数调用协议内部的PC值。这种类型的函数的调用方式与其他函数略有不同,需要专门处理。

在下边的代码中在很多源码场景都出现过,其中有channel的源码,下边看代码,揪出内部实现。

具体作用是要进行并发竞争检测,当race detector被启用时,将记录并获取chansend函数的调用信息和调用位置。

具体来说,该代码用到了Go语言中的race detector,用来检测并发程序中的数据竞争问题。

其中,变量raceenabled用于判断race detector是否被启用。若被启用,则调用racereadpc函数,该函数的作用是向race detector注册读取操作,并记录chansend函数的调用信息和调用位置。这样,race detector就可以通过跟踪这些信息,识别并报告潜在的并发竞争问题。

总之,这段代码用于提高程序对并发竞争问题的检测能力,帮助开发者消除潜在的并发安全隐患。

if raceenabled { 
    racereadpc(c.raceaddr(), callerpc,abi.FuncPCABIInternal(chansend)) 
}

2.3 racenotify

racenotify函数是一个race detector的辅助函数,它用于在发送和接收操作之前或之后通知race detector。

例如,如果我们在执行发送操作之前调用racenotify,则race detector将知道程序正尝试进行发送操作,并可以在需要时捕获任何可能的竞争。

3.例子

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    counter := 0

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            counter++
            wg.Done()
        }()
    }
    wg.Wait()

    fmt.Printf("Counter: %d\n", counter)
}

在上边代码中,我们定义了一个计数器 counter,然后启动了1000个协程,每个协程都将计数器的值增加1,。在主函数中,我们等待所有协程完成,然后打印计数器的值。

我们可以使用Racer Detector竞争检测器来检测是否存在数据竞争问题。在编译和运行代码时,我们可以使用-race选项开启Racer Detector。将上述代码保存为test.go,并在终端执行以下命令:

go run -race test.go

如果输出中有以下一行:

WARNING: DATA RACE

就说明存在数据竞争问题。然后,Racer Detector会告诉我们具体在哪里发生了数据竞争:

WARNING: DATA RACE
Read at 0x00c0000cc00a by goroutine 8:
  main.main.func1()
      /path/to/test.go:12 +0x41

Previous write at 0x00c0000cc00a by goroutine 7:
  main.main.func1()
      /path/to/test.go:12 +0x6b

Goroutine 8 (running) created at:
  main.main()
      /path/to/test.go:19 +0x86

Goroutine 7 (running) created at:
  main.main()
      /path/to/test.go:19 +0x86

这里告诉我们,都有两个goroutine(7和8)争夺了变量counter的访问权限。因为这个例子造成数据竞争的原因非常显而易见,你应该已经明白了如何使用Racer Detector去检测数据竞争问题。