使用
$ go test -race mypkg // test the package
$ go run -race mysrc.go // compile and run the program
$ go build -race mycmd // build the command
$ go install -race mypkg // install the package
实现
竞态探测器集成在go工具链中。当设置了-race命令行标志时,编译器会对所有的内存访问 插入记录内存访问的时间和方式的代码, 而运行时库会监视共享变量的不同步访问。 当检测到这种“racy”行为时,会打印一个警告。 由于其设计、竞态探测器只能在运行代码实际触发时才能检测到竞争条件,这意味着需要在真实的工作负载下运行启用探测器。然而,启用竞态探测的可执行文件可能使用十倍的CPU和内存,因此始终启用探测器是不切实际的。 出于这个困境的一个办法是在启用竞态探测的情况下运行一些测试。负载测试和集成测试是很好的候选者、因为它们往往会执行代码的并发部分。另外的可选途径:生产工作负载环境中,在运行的服务器池中,部署单个启用竞态探测的实例。
The race detector is integrated with the go tool chain. When the -race command-line flag is set, the compiler instruments all memory accesses with code that records when and how the memory was accessed, while the runtime library watches for unsynchronized accesses to shared variables. When such “racy” behavior is detected, a warning is printed. (See this article for the details of the algorithm.)
Because of its design, the race detector can detect race conditions only when they are actually triggered by running code, which means it’s important to run race-enabled binaries under realistic workloads. However, race-enabled binaries can use ten times the CPU and memory, so it is impractical to enable the race detector all the time. One way out of this dilemma is to run some tests with the race detector enabled. Load tests and integration tests are good candidates, since they tend to exercise concurrent parts of the code. Another approach using production workloads is to deploy a single race-enabled instance within a pool of running servers.
Shadow State
Shadow State is N Shadow Words (described below); N is one of 2, 4, 8 (configurable). Every aligned 8-byte word of application memory is mapped into N Shadow Words using direct address mapping (no memory accesses required to compute the shadow address).
Shadow Word is a 64-bit object that contains the following fields:
| TID (Thread Id) | 16 bits (configurable) |
|---|---|
| Scalar Clock | 42 bits (configurable) |
| IsWrite | 1 bit |
| Access Size (1, 2, 4 or 8) | 2 bits |
| Address Offset (0..7) | 3 bits |
One Shadow Word represents a single memory access to a subset of bytes within the 8-byte word of application memory. Therefore the Shadow State describes N different accesses to the corresponding application memory region.
State Machine
The core of the algorithm is the State Machine, a function that updates the Shadow State on every memory access.
First, the thread's clock is incremented and a new Shadow Word that corresponds to the current memory access is created. Then the State Machine iterates over all Shadow Words stored in the Shadow State. If one of the old Shadow Words constitutes a race with the new Shadow Word, a warning message is printed. The new Shadow Word is inserted in place of an empty Shadow Word or in place of a Shadow Word that happened-before the new one. If no place for insertion is found, a random Shadow Word is evicted.
All accesses to Shadow Words are 64-bit atomic loads/stores, but otherwise no locking is involved. Even if two threads are modifying the same Shadow State at the same time, the Shadow State will remain consistent. There is tiny probability to miss a data race though.