LV05-03-线程同步-01-线程同步基础
本文主要是线程同步——线程同步基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
--- | --- |
一、竞争条件
前边我们学习并行和并发的时候知道,在单核 CPU
系统里,为了实现多个程序同时运行的假象,操作系统通常以时间片调度的方式,让每个进程执行每次执行一个时间片,时间片用完了,就切换下一个进程运行,由于这个时间片的时间很短,于是就造成了同时运行的假象。
另外,操作系统也为每个进程创建巨大、私有的虚拟内存的假象,这种地址空间的抽象让每个程序好像拥有自己的内存,而实际上操作系统在背后秘密地让多个地址空间复用物理内存或者磁盘。
如果一个程序只有一个执行流程,也代表它是单线程的。当然一个程序可以有多个执行流程,也就是所谓的多线程程序,线程是调度的基本单位,进程则是资源分配的基本单位。所以,线程之间是可以共享进程的资源,比如代码段、堆空间、数据段、打开的文件等资源,但每个线程都有自己独立的栈空间。
那么问题就出现了,多个线程是有共享资源的,那若是这个线程还没有访问完毕,另一个线程也要访问,要是都是读取那感觉还好,那要是一个写一个读怎么办,这样得到的数据不就有问题了吗?我们可以看一下这个例子:
现在有两个线程,线程1
和线程2
,还有一个全局变量num
,当前num
的值是5
,现在两个线程都是对num
进行+1
操作,我们下边分析一种可能会出现的情况:
将num
加1
在汇编中其实需要三步:
假设我们的线程1
进入这个代码区域,它将num
的值(此时是 5
)从内存加载到它的寄存器中,然后它向寄存器加1
,此时在寄存器中的num
值是 6
。
但是这个时候,时钟中断发生,操作系统将当前正在运行的线程的状态保存到线程的线程控制块 TCB
。而此时线程 2
被调度运行,并进入同一段代码。它也执行了第一条指令,从内存获取num
值并将其放入到寄存器中,此时内存中 num
的值仍为 5
(线程1
还未来得及将6
放回内存),因此线程 2
寄存器中的 num
值也是 5
。假设线程 2
执行接下来的两条指令,将寄存器中的 num
值 + 1
,然后将寄存器中的num
值保存到内存中,于是此时全局变量 num
值是6
。
最后,又发生一次上下文切换,线程 1
恢复执行。还记得它已经执行了两条汇编指令,现在准备执行最后一条指令。线程 1
寄存器中的 num
值是6
,因此,执行最后一条指令后,将值保存到内存,全局变量 num
的值再次被设置为 6
。
两个线程都运行了,都是将num
加1
,理论上说,最终的num
应该为7
才对,但是由于不可控的调度,导致最后 num
值却是 6
。
这样的情况就叫做竞争条件(race condition
),当多线程相互竞争操作共享变量时,在执行过程中发生了上下文切换,我们得到了错误的结果,事实上,每次运行都可能得到不同的结果,因此输出的结果存在不确定性(indeterminate
)。
二、互斥
还是以上边的例子,由于多线程执行操作共享变量的这段代码可能会导致竞争状态,因此我们将此段代码称为临界区(critical section
),它是访问共享资源的代码片段,一定不能让多个线程同时执行。
我们希望这段代码是互斥(mutualexclusion
)的,也就说保证一个线程在临界区执行时,其他线程应该被阻止进入临界区,也就是这段代码执行过程中,最多只能出现一个线程。
三、同步
互斥解决了多线程对临界区的使用问题。这种基于临界区控制的交互作用是比较简单的,只要一个线程进入了临界区,其他试图想进入临界区的线程都会被阻塞着,直到第一个线程离开了临界区。
我们都知道在多线程里,每个线程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个线程能密切合作,以实现一个共同的任务。
例如,线程 1
是负责读入数据的,而线程 2
是负责处理数据的,这两个线程是相互合作、相互依赖的。线程 2
在没有收到线程 1
的唤醒通知时,就会一直阻塞等待,当线程 1
读完数据需要把数据传给线程 2
时,线程 1
会唤醒线程 2
,并把数据交给线程 2
处理。
所谓同步,就是并发线程在一些关键点上可能需要互相等待与互通消息,这种相互制约的等待与互通信息称为线程同步。