Apabila berbilang gorout mengakses data yang sama secara serentak, operasi akses serentak mesti bersiri. Siri baca dan tulis dalam Go boleh dijamin melalui komunikasi saluran atau primitif penyegerakan lain (seperti kunci mutex, kunci baca-tulis dalam pakej penyegerakan dan operasi atom dalam penyegerakan/atom).
Dalam satu goroutine, tingkah laku membaca dan menulis mestilah konsisten dengan perintah pelaksanaan yang ditetapkan oleh program. Dalam erti kata lain, penyusun dan pemproses boleh menyusun semula arahan dalam satu goroutine tanpa mengubah tingkah laku yang ditakrifkan oleh spesifikasi bahasa.
a := 1 b := 2
Disebabkan penyusunan semula arahan, b := 2
boleh mendahului a := 1
Dilaksanakan. Dalam goroutine tunggal, pelarasan perintah pelaksanaan tidak akan menjejaskan keputusan akhir. Tetapi masalah mungkin timbul dalam pelbagai senario goroutine. b := 2
可能先于a := 1
执行。单goroutine中,该执行顺序的调整并不会影响最终结果。但多个goroutine场景下可能就会出现问题。
var a, b int // goroutine A go func() { a := 5 b := 1 }() // goroutine B go func() { for b == 1 {} fmt.Println(a) }()
执行上述代码时,预期goroutine B能够正常输出5,但因为指令重排序,b := 1
可能先于a := 5
func sleep() bool { time.Sleep(time.Second) return true } go fmt.Println(sleep())
b := 1
boleh mendahului a := 5
dilaksanakan dan goroutine B akhirnya boleh mengeluarkan 0. 🎜"Nota: Contoh di atas adalah contoh yang salah dan hanya untuk ilustrasi.
"
Untuk menjelaskan keperluan untuk operasi baca dan tulis, Go memperkenalkan happens before
, yang mewakili kaedah untuk melaksanakan operasi ingatan. .
Jika peristiwa e1 berlaku sebelum peristiwa e2, maka kita katakan e2 berlaku selepas e1. Begitu juga, jika e1 tidak berlaku sebelum e2 atau selepas e2, maka kita katakan bahawa e1 dan e2 berlaku serentak.
r tidak berlaku sebelum w.
Tiada operasi tulis lain berlaku selepas w dan sebelum r.
Operasi tulis lain berlaku sebelum w dan selepas r.
Jika anda keliru, anda betul! Lao Xu juga keliru pada mulanya kedua-dua set syarat ini adalah sama. Atas sebab ini, Lao Xu membandingkannya secara khas berulang kali dengan teks asal untuk memastikan bahawa pemahaman di atas adalah betul.
Mari ubah pemikiran kita dan lakukan penaakulan songsang. Jika dua set syarat adalah sama, maka tidak perlu menulis teks asal dua kali Sudah tentu, perkara itu tidak mudah.
Sebelum meneruskan analisis, saya ingin mengucapkan terima kasih kepada guru Cina saya, tanpa anda, saya tidak akan dapat menemui perbezaan mereka.
r tidak berlaku Sebelum w
, situasi yang mungkin bagi r ialah r berlaku selepas w atau pada masa yang sama dengan w, seperti yang ditunjukkan dalam rajah di bawah (pepejal menunjukkan bahawa ia boleh berlaku pada masa yang sama). r没有发生在w之前
,则r可能的情况是r发生在w之后或者和w同时发生,如下图(实心表示可同时)。
没有其他写操作发生在w之后和r之前
Penyegerakan dalam Go
🎜🎜Berikut adalah beberapa acara penyegerakan yang dipersetujui dalam Go Mereka boleh memastikan bahawa program mengikut prinsip berlaku-sebelum, supaya goroutin serentak agak teratur. 🎜程序初始化运行在单个goroutine中,但是该goroutine可以创建其他并发运行的goroutine。
如果包p导入了包q,则q包init函数执行结束先于p包init函数的执行。main函数的执行发生在所有init函数执行完成之后。
goroutine的创建先于goroutine的执行。老许觉得这基本就是废话,但事情总是没有那么简单,其隐含之意大概是goroutine的创建是阻塞的。
func sleep() bool { time.Sleep(time.Second) return true } go fmt.Println(sleep())
上述代码会阻塞主goroutine一秒,然后才创建子goroutine。
goroutine的退出是无法预测的。如果用一个goroutine观察另一个goroutine,请使用锁或者Channel来保证相对有序。
Channel通信是goroutine之间同步的主要方式。
Channel的发送动作先于相应的接受动作完成之前。
无缓冲Channel的接受先于该Channel上的发送完成之前。
这两点总结起来分别是开始发送
、开始接受
、发送完成
和接受完成
四个动作,其时序关系如下。
开始发送 > 接受完成 开始接受 > 发送完成
“注意:开始发送和开始接受并无明确的先后关系
”
Channel的关闭发生在由于通道关闭而返回零值接受之前。
容量为C的Channel第k个接受先于该Channel上的第k+C个发送完成之前。
这里使用极限法应该更加易于理解,如果C为0,k为1则其含义和无缓冲Channel的一致。
对于任何sync.Mutex或sync.RWMutex变量l以及n < m,第n次l.Unlock()的调用先于第m次l.Lock()的调用返回。
假设n为1,m为2,则第二次调用l.Lock()返回前一定要先调用l.UnLock()。
对于sync.RWMutex的变量l存在这样一个n,使得l.RLock()的调用返回在第n次l.Unlock()之后发生,而与之匹配的l.RUnlock()发生在第n + 1次l.Lock()之前。
不得不说,上面这句话简直不是人能理解的。老许将其翻译成人话:
有写锁时:l.RLock()的调用返回发生在l.Unlock()之后。
有读锁时:l.RUnlock()的调用发生在l.Lock()之前。
“注意:调用l.RUnlock()前不调用l.RLock()和调用l.Unlock()前不调用l.Lock()会引起panic。
”
once.Do(f)中f的返回先于任意其他once.Do的返回。
错误示范一
var a, b int func f() { a = 1 b = 2 } func g() { print(b) print(a) } func main() { go f() g() }
这个例子看起来挺简单,但是老许相信大部分人应该会忽略指令重排序引起的异常输出。假如goroutine f指令重排序后,b=2
先于a=1
发生,此时主goroutine观察到b发生变化而未观察到a变化,因此有可能输出20
。
“老许在本地实验了多次结果都是输出
”00
,20
这个输出估计只活在理论之中了。
错误示范二
var a string var done bool func setup() { a = "hello, world" done = true } func doprint() { if !done { once.Do(setup) } print(a) } func twoprint() { go doprint() go doprint() }
这种双重检测本意是为了避免同步的开销,但是依旧有可能打印出空字符串而不是“hello, world”。说实话老许自己都不敢保证以前没有写过这样的代码。现在唯一能想到的场景就是其中一个goroutine doprint执行到done = true
(指令重排序导致done=true
先于a="hello, world"
执行)时,另一个goroutine doprint刚开始执行并观察到done的值为true从而打印空字符串。
Atas ialah kandungan terperinci Model memori Vernakular Go Happen-Sebelum. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!