操作系统_并发_1.3_不可控调度
关于简单的线程创建无法实现预期结果的分析
1、计数器代码序列
我们来看一个x86的例子来观察一下,为什么会发生这种情况?
mov 0x8049,%eax
add $0x1,%eax
mov %eax,0x8049
这个例子假定,变量counter
位于地址0x8049
。在这三条指令中,先用mov
指令从内存地址处取出值,放入eax
,然后给eax
寄存器的值加1,最后eax
的值被存回内存中相同的地址。
2、基于计数器代码序列的分析
假设我们的线程1,首先进入运行,执行计数器代码/
当执行完+1操作时,发生了时钟中断,此时进行了线程的上下文切换,线程1的状态(程序计数器、寄存器(包括eax寄存器) )被保存,预期的线程1功能并没有完全执行
此时,另一个线程,线程2被选中运行,并进入了同一段计数器的代码,它也执行了第一条指令,获取计数器的值,并将其放入eax寄存器中。
但很遗憾的是,线程1并没有执行将寄存器的值写回到原地址的命令,此时内存对应地址中存放的值仍为50(假设初始值为50),因此当线程2执行完(这里我们假设线程2的执行没有被打断),内存空间中存放的值变为51。
当线程2执行完后,再次发生线程的上下文切换,恢复线程1的运行状态,并继续运行线程1,也就是执行最后一条指令,但是,由于每个线程的寄存器是专用的,此时,线程1的eax的值仍为其本身的值,进行保存后,计数器的值再次被设置为51。
当然这种线程紊乱的情况不唯一,是完全随机,在没有调度的情况下,可能在任一进程的任一指令执行时,被打断。
总体来看,发生的情况是,计数器代码被执行两次,初始值为50,但是结果为51,而预期的正确情况应该是52.
3、竞态条件和临界区
先来看两个概念
- 竞态条件
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。 - 临界区
导致竞态条件发生的代码区称为临界区
临界区是访问共享变量(或者说共享资源)的代码片段,一定不能由多个线程同时执行
很显然,我们上面所展示的情况,就是由于线程的运行结果,取决于代码的时间执行,由于线程执行过程中的上下文切换,导致了错误结果的发生。
4、互斥
- 互斥
所谓互斥,就是指针对于某个系统资源,一个进程或者线程在使用它,另外一个想要使用它的线程,就必须等待,而不能同时使用。
显然,这是我们所希望的一种情况。这个属性保证了如果一个线程在临界区内执行,其他线程都将被阻止进入临界区