https://qiita.com/po3rin/items/18a6621f39e9a6f6f7c4

More than 3 years have passed since last update.

-race とは

package main

import (
    "fmt"
    "strconv"
    "time"
)

func main() {

    m := make(map[string]int)

    go func() {
        for i := 0; i < 1000; i++ {
            m[strconv.Itoa(i)] = i // write
        }
    }()

    go func() {
        for i := 0; i < 1000; i++ {
            fmt.Println(i, m[strconv.Itoa(i)]) // read
        }
    }()

    time.Sleep(time.Second * 5)

}

下記のように実行するとWarningを出します。

-race はそもそも何をしているのか

内部ではC/C++用の競合検出ライブラリが使われています。簡単に言うと、競合を実行時に検出するコードを出力することができるライブラリです。https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual

もちろんGoのコンパイラはこれをサポートしており、実際にruntime/raceREADME.mdにはThreadSanitizerが使われていることが明記されています。

untime/race package contains the data race detector runtime library. It is based on ThreadSanitizer race detector, that is currently a part of the LLVM project (http://llvm.org/git/compiler-rt.git).

コンパイル時に実際にどのようなコードが仕込まれているかを見てみましょう。例えば下記のコードをビルドした場合をみてみます。

普通にビルドすると下記のようにコンパイルされます。

...

pcdata  $2, $1
pcdata  $0, $1
movq    "".x+8(SP), AX
pcdata  $2, $0
incq    (AX)

...

...

pcdata  $2, $1
movq    "".x+32(SP), AX
testb   AL, (AX)
pcdata  $2, $0
movq    AX, (SP)
call    runtime.raceread(SB)
pcdata  $2, $1
movq    "".x+32(SP), AX
movq    (AX), CX
movq    CX, ""..autotmp_4+8(SP)
pcdata  $2, $0
movq    AX, (SP)
call    runtime.racewrite(SB)
movq    ""..autotmp_4+8(SP), AX
incq    AX
pcdata  $2, $2
pcdata  $0, $1
movq    "".x+32(SP), CX

...

コンパイラーがcall runtime.raceread(SB)のように同時に到達可能な各メモリー位置に読み取りおよび書き込みを検知する命令を追加しています。ご覧の通り、-raceをつけると命令が増えてパフォーマンスが落ちるのでビルドしたバイナリを本番に乗っけるのはやめましょう。go build -race でプログラムが遅くなってた話

ちなみにGoコンパイラが吐き出すアセンブリはCompiler Explorerというサービスで簡単に確認できて便利です。

Compiler Explorer