什么是线程安全?一文带你深入理解

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"欢迎来到操作系统系列,采用图解 + 大白话的形式来讲解,让小白也能看懂,帮助大家快速科普入门。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上篇文章有介绍过进程与线程的基础知识,进程下拥有多个线程,虽然多线程间通信十分方便(同进程),但是却带来了线程安全问题,本篇主要就是介绍操作系统中是用什么方法解决多线程安全,废话不多说,进入正文吧。","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"博主希望读者阅读文章后可以养成思考与总结的习惯,只有这样才能把知识消化成自己的东西,而不是单纯的去记忆","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#内容大纲","title":null,"type":null}},{"type":"text","text":"内容大纲","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3a/3adf2b0122fc7d00ec0d7e0119a7bb6d.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#小故事","title":null,"type":null}},{"type":"text","text":"小故事","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"带薪蹲坑,相信都是大伙都爱做的事情,阿星也不例外,但是我司所在的楼层的坑位较少,粥少僧多,十分烦恼。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阿星(线程A)每次去厕所(共享资源),门都是锁着的,说明有同事在里面占着坑(线程B持有锁),只能无奈的在外面乖乖的等着,不久后冲水声响起,同事爽完出来(线程B释放锁),阿星一个健步进入厕所把门锁住(线程A持有锁),享受属于自己的空间,晚来的其他同事只能乖乖排队,一切都是那么井然有序。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假设门锁坏了,井然有序就不存在了,上厕所不再是享受,而是高度紧张,防止门突然被打开,更糟糕的是,开门时,是个妹子,这下不仅仅是线程安全问题,还有数组越界了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"故事说完,扯了那么多,就是想说明,在多线程环境里,对共享资源进行操作,如果多线程之间不做合理的协作(互斥与同步),那么一定会发生翻车现场。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#竞争条件","title":null,"type":null}},{"type":"text","text":"竞争条件","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因为多线程共享进程资源,在操作系统调度进程内的多线程时,必然会出现多线程竞争共享资源问题,如果不采取有效的措施,则会造成共享资源的混乱!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/38/38681579be1e9ec8427c8a000b6c2331.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"来写个小例子,创建两个线程,它们分别对共享变量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 自增 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1000","attrs":{}}],"attrs":{}},{"type":"text","text":" 次,如下代码","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d3/d36a5a37d52100ec8b0537722afb1c90.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正常来说,","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 变量最后的值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2000","attrs":{}}],"attrs":{}},{"type":"text","text":" ,可是并非如此,我们执行下代码看看结果","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结果:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2000","attrs":{}}],"attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结果:","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1855","attrs":{}}],"attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行了两次,结果分别是1855、2000,我们发现每次运行的结果不同,这在计算机里是不能容忍的,虽然是小概率出现的错误,但是小概率它一定是会发生的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#汇编指令","title":null,"type":null}},{"type":"text","text":"汇编指令","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了搞明白到底发生了什么事情,我们必须要了解汇编指令执行,以 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 为例子,汇编指令的执行过程如下","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1f/1f941c76856ca60e0b2ecaa8c9a1e51c.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好家伙,一个加法动作,在 C P U 运行,实际要执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"3","attrs":{}}],"attrs":{}},{"type":"text","text":" 条指令。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在模拟下线程A与线程B的运行,假设此时内存变量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 的值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",线程A加载内存的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值到寄存器,对寄存器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",此时 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",正准备执行下一步寄存器 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值回写内存,时间片使用完了,发生线程上下文切换,保存线程的私有信息到线程控制块T C P。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系统调度线程B执行,此时的内存变量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 依然还是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",线程B执行与线程A一样的步骤,它很幸运,在时间片使用完前,执行完了加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",最终回写内存,内存变量 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"线程B时间片使用完后,发生线程上下文切换,回到线程A上次的状态继续执行,寄存器中的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值回写内存,内存变量再次被设置成 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按理说,最后的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值应该是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"2","attrs":{}}],"attrs":{}},{"type":"text","text":",但是由于不可控的调度,导致最后 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值是 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",下面是线程A与线程B的流程图","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/77/770e57b821d6efe06602ff3f304f05b4.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步:内存取出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值,加载进寄存器","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步:对寄存器内的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值加 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三步:寄存器内的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"i","attrs":{}}],"attrs":{}},{"type":"text","text":" 值取出 加载进内存","attrs":{}}]}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#小结","title":null,"type":null}},{"type":"text","text":"小结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种情况称为竞争条件(race condition),多线程相互竞争操作共享资源时,由于运气不好,在执行过程中发生线程上下文切换,最后得到错误的结果,事实上,每次运行都可能得到不同的结果,因此输出的结果存在不确定性(indeterminate)。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#互斥与同步","title":null,"type":null}},{"type":"text","text":"互斥与同步","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了解决因竞争条件出现的线程安全,操作系统是通过互斥与同步来解决此类问题。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#互斥概念","title":null,"type":null}},{"type":"text","text":"互斥概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多线程执行共享变量的这段代码可能会导致竞争状态,因此我们将此段代码称为临界区(critical section),它是执行共享资源的代码片段,一定不能给多线程同时执行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我们希望这段代码是互斥(mutualexclusion)的,也就说执行临界区(critical section)代码段的只能有一个线程,其他线程阻塞等待,达到排队效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6e/6e7ee8d72af4ff05b4c6d8d18d7d2fd8.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥并不只是针对多线程的竞争条件,同时还可用于多进程,避免共享资源混乱。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#同步概念","title":null,"type":null}},{"type":"text","text":"同步概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥解决了「多进程/线程」对临界区使用的问题,但是它没有解决「多进程/线程」协同工作的问题","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们都知道在多线程里,每个线程一定是顺序执行的,它们各自独立,以不可预知的速度向前推进,但有时候我们希望多个线程能密切合作,以实现一个共同的任务。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所谓同步,就是「多进程/线程间」在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为「进程/线程」同步。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"举个例,有两个角色分别是研发、质量管控,质量管控测试功能,需要等研「发完成开发」,研发要修bug也要等质量管控「测试完成提交B U G」,正常流程是研发完成开发,通知质量管控进行测试,质量管控测试完成,通知研发人员修复bug。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b9/b9705a6119c71f633fc0c101128b2e27.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#互斥与同步的区别","title":null,"type":null}},{"type":"text","text":"互斥与同步的区别","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥:某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的(操作 A 和操作 B 不能在同一时刻执行)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同步:互斥的基础上,通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥(操作 A 应在操作 B 之前执行,操作 C 必须在操作 A 和操作 B 都完成之后才能执行)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要按照某种次序来运行相应的线程(也是一种互斥)!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"link","attrs":{"href":"#互斥与同步的实现","title":null,"type":null}},{"type":"text","text":"互斥与同步的实现","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"互斥与同步可以保证「多进程/线程间正确协作」 ,但是互斥与同步仅仅只是概念,操作系统必须要提供对应的实现,针对互斥与同步的实现有下面两种","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"锁:加锁、解锁操作(互斥)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"信号量:P、V 操作(同步)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这两个种方式都可以实现「多进程/线程」互斥,信号量比锁的功能更强一些,它还可以方便地实现「多进程/线程」同步。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#锁","title":null,"type":null}},{"type":"text","text":"锁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顾名思义,给临界区上一把锁,任何进入临界区)的线程,必须先执行加锁操作,加锁成功,才能进入临界区,在离开临界区时再释放锁,达到互斥的效果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1ae9e80742dda66993fab20d09ae60f3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"锁的实现方式又分为「忙等待锁」和「无忙等待锁」","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#忙等锁","title":null,"type":null}},{"type":"text","text":"忙等锁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"检查并设置(test-and-set-lock,TSL)是一种不可中断的原子运算,它属于原子操作指令,可以通过它来实现忙等锁(自旋锁)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"test-and-set-lock 指令伪代码","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/de/de1bb9c92a3c7e7cdec2a9313d73f8e3.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"检查并设置做了如下几个步骤","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"检查旧值是否相等","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相等设置新值,返回原旧值(成功)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不相等,无任何操作,直接返回原旧值(失败)","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的步骤,把它看成一步并具备原子性,原子性的意思是指全部执行或都不执行,不会出现执行到一半的中间状态.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"伪代码","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"testAndSetLock","attrs":{}}],"attrs":{}},{"type":"text","text":"实现忙等锁(自旋锁)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1b/1b80d15f14fd9c25f78d4bd5ecd31f3b.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面两种场景运行","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"单线程:假设一个线程访问临界区,执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,检查旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 通过,更新原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 为新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",返回原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",获取锁成功,离开临界区时,执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,检查旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 通过,更新原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 为新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",释放锁成功。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多线程:假设两个线程,线程A访问临界区,执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,检查旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 通过,更新原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":" 为新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",返回原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",获取锁成功,此时线程B执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,旧值检查失败,获取锁失败,一直循环直到更新成功为止,当线程A离开临界区时,执行 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"unLock","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法,检查旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 通过,更新原旧值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":" 为新值 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"0","attrs":{}}],"attrs":{}},{"type":"text","text":",释放锁成功,线程B获取锁成功。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当获取不到锁时,线程就会一直 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"wile","attrs":{}}],"attrs":{}},{"type":"text","text":" 循环,不做任何事情,所以就被称为忙等待锁,也被称为自旋锁。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是最简单的锁,一直自旋,利用 C P U 周期,直到锁可用。在单处理器上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在 C P U 上无法使用,因为一个自旋的线程永远不会放弃 C P U。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"link","attrs":{"href":"#无忙等锁","title":null,"type":null}},{"type":"text","text":"无忙等锁","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顾名思义,无忙等锁不需要主动自旋,被动等待唤醒即可,在没有获取到锁的时候,就把该线程加入到等待队列,让出 C P U 给其他线程,其他线程释放锁时,再从等待队列唤醒该线程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/78/78da1292367b5cd5300c717f256e9eaf.png","alt":"","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"两种锁的实现都是基于检查并设置(test-and-set-lock,TSL),上面只是简单的伪代码,实际上操作系统的实现会更复杂,但是基本思想与大致流程还是与本例一样。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"link","attrs":{"href":"#信号量","title":null,"type":null}},{"type":"text","text":"信号量","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"操作系统中协调「多线程/进程」共同配合工作,就是通过信号量实现的,通常信号量代表「资源数量」,对应一个整型(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":")变量,还有两个原子操作的系统调用函数来控制「资源数量」。","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"P 操作:将 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":" 减 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"1","attrs":{}}],"attrs":{}},{"type":"text","text":",相减后,如果 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"s e n","attrs":{}}],"attrs":{}},{"type":"text","text":" 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章