Go 的内存模型指定一系列条件,保证「在一个 goroutine 中读取变量可以观察到其他 goroutine 中对该变量所写的值」即安全的在不同的协程中读写变量
🌰
func main() {
var wg sync.WaitGroup
var count int
var ch = make(chan bool, 1)
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
ch <- true // 并不会被阻塞,存在竞争
count++ // 1
time.Sleep(time.Millisecond)
count-- // 2
<-ch
wg.Done()
}()
}
wg.Wait()
}
这里把buffered channel作为 semaphore 来使用,表面上看最多允许一个goroutine对count进行++和--,但其实这里是有bug的。根据Go语言的内存模型,对count变量的访问并没有形成临界区。编译时开启竞态检测可以看到这段代码有问题:
go run -race test.go
编译器可以检测到 1, 2 两处存在竞态条件,也就是count并没像我们想要的那样在临界区执行。
假设 A 和 B 表示一个多线程的程序执行的两个操作。如果 A happens-before B,那么 A 操作对内存的影响 将对执行 B 的线程(且执行 B 之前)可见。
int A, B;
void foo()
{
// This store to A ...
A = 5;
// ... effectively becomes visible before the following loads. Duh!
B = A * A;
}
happens-before 是一系列语言规范中定义的操作间的关系,它和时间的概念独立
关于channel的happens-before在Go的内存模型中提到了三种情况: