The happens before relation is defined as the transitive closure of the union of the sequenced before and synchronized before relations.
翻译:
happens before关系被定义为序列化before关系和同步化before关系的联合的传递闭包。
解析:
可以理解为程序的执行顺序,在单个协程当中,程序先行发生的顺序就是程序表达的顺序
举个例子:
var a string
func hello() {
a = "hello, world"
print(a)
}
我们说a = "hello, world"
,先行发生于print(a)
。
var a string
func f() {
print(a)
}
func f2() {
a = "hello, world"
}
func hello() {
go f()
go f2()
}
在多个协程对共享变量的读写中,为了保证读写的正确性,我们需要引入同步机制保证程序的顺序一致性执行。比如channel,sync、atomic package。在上面的例子中f()有可能打印空字符串或者"hello, world"
,随机的。print(a)
不先行发生于a赋值
,a赋值
也不先行发生于print(a)
,我们就说这是并发的。我们应该如何保证f2的写入一定能被f()看到呢?这就是我们要讨论的内容。
以下是一些我们会用到的让程序保证顺序一致性执行的一些常用手段
一个函数的初始化函数可能在单个goroutine中执行,但是执行过程中有可能会开启另外一个协程并发执行,这时:
p引入包q,q的init函数结束先行发生于q的所有init函数的开始
所有的init函数执行完了才会执行main函数
新协程的创建先行发生与该协程的执行
var a string
func f() {
print(a)
}
func hello() {
a = "hello, world"
go f()
}
这里f()一定能打印"hello, world"
,因为a
的赋值先行发生于协程的创建,而协程的创建先行发生于函数的执行。
var a string
func f() {
print(a)
}
func hello() {
go f()
a = "hello, world"
}
如果我们调整顺序
var a string
func f() {
print(a)
}
func hello() {
go f()
a = "hello, world"
}
结果是随机的
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
PS E:\code\go\go_study\advanced\memery_model> go run memery.go
hello, world
也就是说gorountine 的退出不会保证先行发生于程序的任何事件
var c = make(chan int)
var a string
func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
<-c
先行发生于c <- 0
,所以a一定能打印 "hello, world"
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world"
c <- 0
}
func main() {
go f()
<-c
print(a)
}
c <- 0
先行发生于<-c
,所以a一定能打印 "hello, world"
容量为c的带缓冲chan的第k个接收,先行发生于第c+k个发送
var limit = make(chan int, 3)
func main() {
for _, w := range work {
go func(w func()) {
limit <- 1
w()
<-limit
}(w)
}
select{}
}
这里当w()执行耗时任务,chan队列满了,第四个任务的 w 的 <-lmit 先行发生于 limit <- 1
, 这样就能保证我们队列中只有3个任务在同时执行。
var l sync.Mutex
var a string
func f() {
a = "hello, world"
l.Unlock()
}
func main() {
l.Lock()
go f()
l.Lock()
print(a)
}
For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() is synchronized before call m of l.Lock() returns.
l执行了n次Unlock, n次Unlock先行发生于n+1次Lock操作
对于同一个sync.RWMutex变量l:
var a string
var once sync.Once
func setup() {
a = "hello, world"
}
func doprint() {
once.Do(setup)
print(a)
}
func twoprint() {
go doprint()
go doprint()
}
once 可以实现多个协程只执行一次setup代码,并且其他协程会阻塞直到setup函数返回才会继续运行下面的代码。
go
var counter int64
func worker() {
for {
// 原子递增计数器
atomic.AddInt64(&counter, 1)
}
}
func main() {
for i := 0; i < 10; i++ {
go worker()
}
time.Sleep(time.Second)
// 原子读取计数器
c := atomic.LoadInt64(&counter)
fmt.Println(c)
}
相当于每次执行前都对变量加锁了。比如我们熟悉的i++,汇编成代码其实是3条指令,他是有可能被程序中断的,而atomic 可以实现原子性操作。
https://go.dev/ref/mem
https://www.jianshu.com/u/89aa91463068