funcmain() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { gofunc() { fmt.Println(i) // Not the 'i' you are looking for. wg.Done() }() } wg.Wait() }
如果你去找一些 go 的面试题,肯定能找到类似的例子,然后会问你这里会输出什么? 常见的答案就是会输出 5 个 5,因为在 for 循环的 i++ 会执行的快一些,所以在最后打印的结果都是 5 这个答案不能说不对,因为真的执行的话大概率也是这个结果,但是不全 因为这里本质上是有数据竞争,在新启动的 goroutine 当中读取 i 的值,在 main 中写入,导致出现了 data race,这个结果应该是不可预知的,因为我们不能假定 goroutine 中 print 就一定比外面的 i++ 慢,习惯性的做这种假设在并发编程中是很有可能会出问题的
1 2 3 4 5 6 7 8 9 10 11
funcmain() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { gofunc(i int) { fmt.Println(i) wg.Done() }(i) } wg.Wait() }
这个要修改也很简单,只需要将 i 作为参数传入即可,这样每个 goroutine 拿到的都是拷贝后的数据
// ParallelWrite writes data to file1 and file2, returns the errors. funcParallelWrite(data []byte)chanerror { res := make(chan error, 2) f1, err := os.Create("/tmp/file1") if err != nil { res <- err } else { gofunc() { // 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("/tmp/file2") // The second conflicting write to err. if err != nil { res <- err } else { gofunc() { _, err = f2.Write(data) res <- err f2.Close() }() } return res }
我们使用 go run -race main.go 执行,可以发现这里报错的地方是,19 行和 24 行,有 data race,这里主要是因为共享了 err 这个变量
================== WARNING: DATA RACE Write at 0x00c0000a01a0 by goroutine 7: main.ParallelWrite.func1() /home/ll/project/Go-000/Week03/blog/03_data_race/03/main.go:19 +0x94
Previous write at 0x00c0000a01a0 by main goroutine: main.ParallelWrite() /home/ll/project/Go-000/Week03/blog/03_data_race/03/main.go:24 +0x1dd main.main() /home/ll/project/Go-000/Week03/blog/03_data_race/03/main.go:6 +0x84
Goroutine 7 (running) created at: main.ParallelWrite() /home/ll/project/Go-000/Week03/blog/03_data_race/03/main.go:16 +0x336 main.main() /home/ll/project/Go-000/Week03/blog/03_data_race/03/main.go:6 +0x84 ================== Found 1 data race(s) exit status 66
细心的同学可能会有这个疑问,在 24 行不也是重新赋值了么,为什么在这里会和 19 行产生 data race 呢? 这是由于 go 的语法规则导致的,我们在初始化变量的时候如果在同一个作用域下,如下方代码,这里使用的 err 其实是同一个变量,只是 f1 f2 不同,具体可以看 effective go 当中 Redeclaration and reassignment 的内容
type IceCreamMaker interface { // Great a customer. Hello() }
type Ben struct { name string }
func(b *Ben)Hello() { fmt.Printf("Ben says, \"Hello my name is %s\"\n", b.name) }
type Jerry struct { name string }
func(j *Jerry)Hello() { fmt.Printf("Jerry says, \"Hello my name is %s\"\n", j.name) }
funcmain() { var ben = &Ben{name: "Ben"} var jerry = &Jerry{"Jerry"} var maker IceCreamMaker = ben
var loop0, loop1 func()
loop0 = func() { maker = ben go loop1() }
loop1 = func() { maker = jerry go loop0() }
go loop0()
for { maker.Hello() } }
这个例子有趣的点在于,最后输出的结果会有这种例子
1 2
Ben says, "Hello my name is Jerry" Ben says, "Hello my name is Jerry"
这是因为我们在 maker = jerry 这种赋值操作的时候并不是原子的,在上一篇文章中我们讲到过,只有对 single machine word 进行赋值的时候才是原子的,虽然这个看上去只有一行,但是 interface 在 go 中其实是一个结构体,它包含了 type 和 data 两个部分,所以它的复制也不是原子的,会出现问题
1 2 3 4
typeinterfacestruct { Type uintptr// points to the type of the interface implementation Data uintptr// holds the data for the interface's receiver }