Data Race Detector

Introduction

数据争用是并发系统中最常见且最难调试的错误类型之一. 当两个goroutine并发访问同一变量并且至少其中之一是写操作时,就会发生数据争用. 有关详细信息,请参见Go内存模型 .

这是一个数据争用的示例,它可能导致崩溃和内存损坏:

func main() {
	c := make(chan bool)
	m := make(map[string]string)
	go func() {
		m["1"] = "a" // First conflicting access.
		c <- true
	}()
	m["2"] = "b" // Second conflicting access.
	<-c
	for k, v := range m {
		fmt.Println(k, v)
	}
}

Usage

为了帮助诊断此类错误,Go包含一个内置的数据竞争检测器. 要使用它,请将-race标志添加到go命令:

$ go test -race mypkg    // to test the package
$ go run -race mysrc.go  // to run the source file
$ go build -race mycmd   // to build the command
$ go install -race mypkg // to install the package

Report Format

当竞争检测器在程序中找到数据竞争时,它将打印报告. 该报告包含用于冲突访问的堆栈跟踪,以及在其中创建了所涉及的goroutine的堆栈. 这是一个例子:

WARNING: DATA RACE
Read by goroutine 185:
  net.(*pollServer).AddFD()
      src/net/fd_unix.go:89 +0x398
  net.(*pollServer).WaitWrite()
      src/net/fd_unix.go:247 +0x45
  net.(*netFD).Write()
      src/net/fd_unix.go:540 +0x4d4
  net.(*conn).Write()
      src/net/net.go:129 +0x101
  net.func·060()
      src/net/timeout_test.go:603 +0xaf

Previous write by goroutine 184:
  net.setWriteDeadline()
      src/net/sockopt_posix.go:135 +0xdf
  net.setDeadline()
      src/net/sockopt_posix.go:144 +0x9c
  net.(*conn).SetDeadline()
      src/net/net.go:161 +0xe3
  net.func·061()
      src/net/timeout_test.go:616 +0x3ed

Goroutine 185 (running) created at:
  net.func·061()
      src/net/timeout_test.go:609 +0x288

Goroutine 184 (running) created at:
  net.TestProlongTimeout()
      src/net/timeout_test.go:618 +0x298
  testing.tRunner()
      src/testing/testing.go:301 +0xe8

Options

GORACE环境变量设置种族检测器选项. 格式为:

GORACE="option1=val1 option2=val2"

选项包括:

Example:

$ GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race

Excluding Tests

使用-race标志进行构建时, go命令会定义其他构建标签 race . 您可以在运行种族检测器时使用标记来排除一些代码和测试. 一些例子:

// +build !race

package foo

// The test contains a data race. See issue 123.
func TestFoo(t *testing.T) {
	// ...
}

// The test fails under the race detector due to timeouts.
func TestBar(t *testing.T) {
	// ...
}

// The test takes too long under the race detector.
func TestBaz(t *testing.T) {
	// ...
}

How To Use

首先,使用竞赛检测器运行测试( go test -race ). 种族检测器仅查找在运行时发生的种族,因此无法在未执行的代码路径中找到种族. 如果测试的覆盖范围不完整,则可以在实际的工作量下运行使用-race构建的二进制文件,从而发现更多的问题.

Typical Data Races

这是一些典型的数据竞赛. 所有这些都可以使用种族检测器进行检测.

Race on loop counter

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func() {
			fmt.Println(i) // Not the 'i' you are looking for.
			wg.Done()
		}()
	}
	wg.Wait()
}

函数文字中的变量i与循环使用的变量相同,因此goroutine中的读取与循环增量竞争. (此程序通常打印55555,而不是01234.)可以通过复制变量来修复该程序:

func main() {
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i++ {
		go func(j int) {
			fmt.Println(j) // Good. Read local copy of the loop counter.
			wg.Done()
		}(i)
	}
	wg.Wait()
}

Accidentally shared variable

// ParallelWrite writes data to file1 and file2, returns the errors.
func ParallelWrite(data []byte) chan error {
	res := make(chan error, 2)
	f1, err := os.Create("file1")
	if err != nil {
		res <- err
	} else {
		go func() {
			// This err is shared with the main goroutine,
			// so the write races with the write below.
			_, err = f1.Write(data)
			res <- err
			f1.Close()
		}()
	}
	f2, err := os.Create("file2") // The second conflicting write to err.
	if err != nil {
		res <- err
	} else {
		go func() {
			_, err = f2.Write(data)
			res <- err
			f2.Close()
		}()
	}
	return res
}

解决方法是在goroutines中引入新变量(注意使用:= ):

			...
			_, err := f1.Write(data)
			...
			_, err := f2.Write(data)
			...

Unprotected global variable

如果从多个goroutine中调用以下代码,则会导致service映射上的争用. 同时读取和写入同一映射不安全:

var service map[string]net.Addr

func RegisterService(name string, addr net.Addr) {
	service[name] = addr
}

func LookupService(name string) net.Addr {
	return service[name]
}

为了使代码安全,请使用互斥锁保护访问:

var (
	service   map[string]net.Addr
	serviceMu sync.Mutex
)

func RegisterService(name string, addr net.Addr) {
	serviceMu.Lock()
	defer serviceMu.Unlock()
	service[name] = addr
}

func LookupService(name string) net.Addr {
	serviceMu.Lock()
	defer serviceMu.Unlock()
	return service[name]
}

Primitive unprotected variable

数据争用也可能发生在原始类型的变量( boolintint64等)上,如以下示例所示:

type Watchdog struct{ last int64 }

func (w *Watchdog) KeepAlive() {
	w.last = time.Now().UnixNano() // First conflicting access.
}

func (w *Watchdog) Start() {
	go func() {
		for {
			time.Sleep(time.Second)
			// Second conflicting access.
			if w.last < time.Now().Add(-10*time.Second).UnixNano() {
				fmt.Println("No keepalives for 10 seconds. Dying.")
				os.Exit(1)
			}
		}
	}()
}

即使是这种"无辜的"数据竞争也可能导致难以调试的问题,这些问题是由内存访问的非原子性,干扰编译器优化或重新排序访问处理器内存的问题引起的.

此竞赛的典型解决方法是使用通道或互斥锁. 为了保持无锁行为,还可以使用sync/atomic程序包.

type Watchdog struct{ last int64 }

func (w *Watchdog) KeepAlive() {
	atomic.StoreInt64(&w.last, time.Now().UnixNano())
}

func (w *Watchdog) Start() {
	go func() {
		for {
			time.Sleep(time.Second)
			if atomic.LoadInt64(&w.last) < time.Now().Add(-10*time.Second).UnixNano() {
				fmt.Println("No keepalives for 10 seconds. Dying.")
				os.Exit(1)
			}
		}
	}()
}

Supported Systems

竞赛检测器可在linux/amd64linux/ppc64lelinux/arm64freebsd/amd64netbsd/amd64darwin/amd64windows/amd64 .

Runtime Overhead

竞争检测的成本因程序而异,但是对于典型的程序,内存使用量可能会增加5-10倍,执行时间可能会增加2-20倍.

by  ICOPY.SITE