线程被信号量唤醒后从何处返回执行?信号量执行逻辑答疑
信号量核心语义纠错与问题场景正确执行逻辑
核心认知偏差说明
你之前记忆的信号量操作逻辑存在关键错误,这是所有推演矛盾的根源。P、V操作都是不可分割的原子操作,正确逻辑如下:
P(sem)执行逻辑:
原子判断信号量值,若值大于0则将值减1后直接返回,线程继续执行后续代码;若值为0,将当前线程加入信号量的等待队列,线程在当前P调用位置阻塞。注意:线程阻塞在P操作上时,相当于已经提交了配额申请,后续释放的配额会优先分配给等待队列中的线程。V(sem)执行逻辑:
原子判断信号量等待队列,若队列非空,直接从队列中取出一个等待线程唤醒,不额外增加信号量计数值(相当于把当前释放的配额直接转交给被唤醒线程);若等待队列为空,才将信号量值加1。
关于阻塞返回位置可以明确:线程在哪个P调用位置阻塞,被唤醒后就直接从该P调用的下一条语句继续执行——唤醒动作本身就代表这个阻塞的P操作已经成功获取了信号量配额,不需要重新执行该P操作,也不会跳转到其他代码位置。
之前推演的矛盾来源
你之前假设V操作会先把信号量值加1,再判断是否唤醒线程,且阻塞的P操作没有完成配额扣减,这个错误逻辑才会导致“信号量累加到2”的问题。在正确语义下,P操作的配额申请和V操作的配额释放,在有等待线程时是直接交接的,不会出现计数错误:
以标准P-临界区-V范式为例:线程A卡在P(s)位置时s=0,线程B执行V(s)时发现有等待线程,直接唤醒A,s值保持0不变;A被唤醒后直接进入临界区,退出时执行V(s),此时若无其他等待线程,s值加为1,整个流程计数完全正确,不存在累加错误。
代码场景的正确执行流程与输出
初始状态:信号量s=0、m=1,thread1循环执行function1,thread2循环执行function2。
- 无论线程启动顺序如何,thread1第一次进入循环执行第一个
P(s)时,s初始值为0,必然会在第一个P(s)位置阻塞,加入s的等待队列。 - thread2进入循环执行
P(m):m初始值为1,减为0后返回,打印字符b;执行V(m)时m无等待线程,m值加回1;执行V(s)时发现s等待队列有阻塞的thread1,直接唤醒thread1,s值保持0不变。 - thread1被唤醒,从第一个P(s)的下一句也就是第二个
P(s)开始执行:此时s值为0,thread1在第二个P(s)位置再次阻塞,加入s的等待队列。 - thread2完成第一次循环后回到循环开头,再次执行
P(m):m值为1,减为0后返回,打印第二个b;执行V(m)将m值加回1;执行V(s)时发现s等待队列有卡在第二个P(s)的thread1,直接唤醒thread1,s值依然保持0不变。 - thread1被唤醒,从第二个P(s)的下一句开始执行:执行
P(m)将m从1减为0,打印字符a;执行V(m)将m值加回1,回到while循环开头,再次遇到第一个P(s),因s值为0第三次阻塞。 - 后续流程完全重复上述步骤:thread2每打印2次
b,thread1才能依次通过两个P(s)阻塞点,打印1次a。
最终程序输出不存在随机顺序,固定为bbabbabba...的模式,既不会出现乱序的a、b混合,也不会只输出b。
内容的提问来源于stack exchange,提问作者eh-chwi
相关产品推荐
相关产品推荐

