go语言happens-before原则及应用_第1页
go语言happens-before原则及应用_第2页
go语言happens-before原则及应用_第3页
go语言happens-before原则及应用_第4页
go语言happens-before原则及应用_第5页
已阅读5页,还剩8页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、引言先抛开你所熟知的信号量、锁、同步原语等技术,思考这个问题:如何保证并发 读写的准确性? 一个没有任何并发编程经验的程序员可能会觉得很简单:这有什 么问题呢,同时读写能有什么问题,最多就是读到过期的数据而已。一个理想的 世界当然是这样,只可惜实际上的机器世界往往隐藏了很多不容易被发觉的事情。 至少有两个行为会影响这个结论:编译器往往有指令重排序的优化;例如程序员看到的源代码是好句以4;,而实际上执行的顺序可能是以4;好3;,这是因为编译器为了优化执行效 率可能对指令进行重排序; 高级编程语言所支持的运算往往不是原子化的;例如a += 3实际上包含了读变量、加运算和写变量三次原子操作。既然整个

2、过程并不是原子化的,就意味着随时有其它入侵者侵入修改数据。更为隐藏的例子:对于变量 的读写甚至可能都不是原子化的。不同机器读写变量的过程可能是不同的, 有些机器可能是64位数据一次性读写,而有些机器是32位数据一次读写。这就意味着一个64位的数据在后者的读写上实际上是分成两次完成的!试想,如果你试图读取一个64位数据的值,先读取了低32的数据,这时另一个线程切进来修改了整个数据的值,最后你再读取高32的值,将高32和低32的数据拼成完整的值,很明显会得到一个预期以外的数 据。看起来,整个并发编程的世界里一切都是不确定的,我们不知道每次读取的变量 到底是不是及时、准确的数据。幸运的是,很多语言都

3、有一个kappehs-before的 规那么,能帮助我们在不确定的并发世界里寻找一丝确定性。happens-before你可以把ka叩看作一种特殊的比拟运算,就好像、一样。对应的,还有happe八s-aftec它们之间的关系也好像、一样:Oncesync中还提供了一个。八cc的数据结构,用于控制并发编程中只执行一次的逻辑,例如:var a striigvair oince sg八八ccfeme setapO (a = kelloj world11认土仇(set up11)fiAM do”int() o 八 ce.Do(s 血?)fmt.Prmtfn(a)Mac twopsMt。go dopHn

4、t。go dopHht。)会打印 hello, world”两次和“set up”一次。八”的happens-be- 规那么也很直观:第一次执行。八ccQo happen-before其余的。八ceQo应用掌握了上述的基本k即ins-bcf”c规那么,可以结合起来分析更复杂的场景了,来 看这个例子:var a, b Mtfeme f() a = 1 / (I)b = 2/ (2)心八cgO print(b) / priit(a) / (4)fiAAC kvaii0 go K)gO)这里ka眸ChS-befC (2),ka叩c八s-bcf”c(4),但是与、之间以及与 (3)、(4)之间并没有k

5、appc八s-bcf”c关系,这时候结果是不确定的,一种有趣的结 果是2、0,也就是(1)、(2)之间发生了指令重排序。现在让我们修改一下上面的 代码,让它按我们预期的逻辑运行:要么打印0、0,要么打印1、2o使用锁var a, b litvar lock Mutexfeme f()lock.LockQ / (2)4二工(2)b = 2 / lock.Unlock() / (4)心八c gO lock.LockO / priit(b) / (6)print / (7)(ock.Un(ock() / (8)八c kvxaiiaQ go fogO)回想下锁的规那么:1.对锁实例调用八次Unlock

6、 happ。八s-bcf。匕调用Lock作次,只要八这里,存在两种可能:要么(4) happens-before,要么happen -before,会分 别推导出两种结果: happe八s-bcfc (7) happens-before (2) happens-before (3), 以及(2) happe八s-before happens-befove (6) kappe八s-before (7),也就分别对应“0、 0和 1、2两种结果。使用通道 var a, b ivtvar c = iake(cka ict, 1) feme f() -c4 = 1(2)。= 2 / c一1)心八c g

7、O -cprint(b) / (6)priit(a) / (7)c b是否成立的过程,我们的做法很简单:.基于一些简单的公理;例如自然数的自然大小:/2工.基于比拟运算符的传递性,也就是如果。泌且分c,那么ac判断a happen-before b的过程也是类似的:根据一些简单的明确的happens-before 关系,再结合ka晔的传递性,推导出我们所关心的W和r之间的 kappens-before 关系。happc八s-before 传递性:如果 a happe八s-befoK。b,且 b happe八s-before C, 贝lj a happe八s-bef。丫。C因此我们只需要了解这些

8、明确的ha叩加关系,就能在并发世界里寻找到 珍贵确实定性了。go语言中的happens-before关系具体的happens-before关系是因语言而异的,这里只介绍go语言相关的规那么, 感兴趣可以直接阅读官方文档,有更完整、准确的说明。自然执行首先,最简单也是最直观的人即pens-before规那么:在同一个goroutine里,书写在前的代码happe八s-before书写在后 的代码。例如:a = 3; / (圾=4; / (2)那么Q) kappcns-bcforc (2)。我们上面提到指令重排序,也就是实际执行的顺序与书 写的顺序可能不一致,但h叩pens-before与指令重排

9、序并不矛盾,即使可能发 生指令重排序,我们依然可以说(1) happens-before (2)。初始化每个g。文件都可以有一个加土方法,用于执行某些初始化逻辑。当我们开始执 行某个小川认方法时,go会先在一个goroutine里做初始化工作,也就是执行所 有go文件的i八江方法,这个过程中go可能创立多个goroutine并发地执行,因此通常情况下各个知t方法是没有人叩飞八s-before关系的。关于,八才方法有两条happe八s-before 规那么:.a包导入了 b包,此时b包的iiit方法happe八s-before 3包的所有代码;.所有iiit方法happe八s-before恒/认

10、方法;goroutinegoroutine相关的规那么主要是其创立和销毁的:.goroutine 的创立 happens-砥。丫。其执行;.goroutine的完成不保证k叩.八s-befo%任何代码;第一条规那么举个简单的例子即可:var a stringface f() / (工)feme kelloQ a = heH。, world / (2)go f() )因为goroutine的创立happe八s-before其执行,所以happe八s-before (1),又因为自然执行的规那么(2) happe八s-before,根据传递性,所以happens-before (1),这样保证了我

11、们每次打印出来的都是“hello world”而不是空字符串。第二条规那么是少见的否认句式,同样举个简单的例子: fu 八c kelloQ go 4 二keo |() / (工)仇力八土伉(a) / (2)由于goroutine的完成不保证happens-before任何代码,因此(1) happens-before不成立,这样我们就不能保证每次打印的结果都是“hell。”。通道通道channel是go语言中用于goroutine之间通信的主要渠道,因此理解通道 之间的happens-before规那么也至关重要。1 .对于缓冲通道,向通道发送数据 叱吟-砥。匕从通道接收到数据结合一个例子:v

12、ar c =八 int, LO)vair a string fuM f() a = helloj world / (1)C - O / (2)go fo / -c / (4)/ (5)c是一个缓冲通道,因此向通道发送数据ka叩ChS-befc从通道接收到数据,也就 是happen-More (4),再结合自然执行规那么以及传递性不难推导出Q) happens-before (5),也就是打印的结果保证是hello world。有趣的是,如果我们把C的定义改为var c =3砥左加血)也就是无缓冲通道,上 面的结论就不存在了(注1),打印的结果不一定为“hello world”,这是因为:.对于

13、无缓冲通道,从通道接收数据-before向通道发送数 据我们可以将上述例子稍微调整下:var c = kvake(cha a sWMgfeme f() a = heH。,world11 / (2)-c / (2)flAiaCgo f。c w := range work go ftmc。)limit - 1 / (1)w。 (2)/ Kw)select我们先套用一下上面的规那么,贝IJ:第1次第4次、第2次 (3)kappeias-before第5次、第3次happcns-before第6次,再结合传递性:”第 1 次(2)ka叩cns-befoHC 第 1 次happens-before 第

14、4 次Q)kappc八s-bcforc 第 4 次、第 2 次(2)happens -before 第 2 次happens -before 第 5 次 (l)kappens-before 第 5 次(2)”,简单地说:第 1 次happens-before 第 4 次、”第2次(2)哂加* 第5次、第3次(2於-before第6次”这样 我们虽然没有做任何分批,却事实上将workers分成三个一批、每批并发地执行。 这就是通过这条happens-before规那么保证的。这个规那么理解起来其实也很简单,C是通道的容量,如果无法保证第k次接收 happens-before第k+C次发送,那通道

15、的缓冲就不够用了。注1:以上是官方文档给的规那么和例子,但是笔者在尝试将第一个 例子的c改成无缓冲通道后发现每次打印的依然稳定是hell。world,并没有出现预期的空字符串,也就是看起来happens-before 规那么依然成立。但既然官方文档说无法保证,那我们开发时还是按 照happens-before不成立比拟好。锁锁也是并发编程里非常常用的一个数据结构。go语言中支持的锁主要有两种: sgnc.Mutcx和sg八c.RWMutex,即普通锁和读写锁(读写锁的原理可以参见另一篇文 章)。普通锁的happens-before规那么也很直观:1.对锁实例调用八次Unlock happen-before调用Lock m次,只要八请看这个例子:var I yic.Mutexvair a string fimc f() a = hello,world11 / (1)I.UnlockQ / (2)fix八。加加八0 LLock。 / go f()/ (4)I.LockQ / priit(a

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论