单二元信号量实现奇偶线程交替打印失败问题咨询
问题
编写了ODD和EVEN两个线程,预期它们能连续交替打印奇数和偶数。使用初始值为1的二元信号量mutex,但无论哪个线程先运行,程序仅会打印一次“Even : 0”,ODD线程始终无法启动——即便此时变量i已变为1,且mutex已被释放。
新增一个初始值为0的信号量,并在EVEN线程末尾向ODD线程发送信号后,程序就能正常运行。疑问是:当EVEN线程释放mutex并递增i后,ODD线程为何仍无法继续执行?
附上相关C语言代码:
// C program to demonstrate working of Semaphores #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> sem_t mutex,OE; int i=0, n=50; void* odd(void* arg) { while(n--) { //sem_wait(&OE); while(i % 2 == 0) sem_wait(&mutex); printf("Odd : %d \n",i); i += 1; sem_post(&mutex); } } void* even(void* arg) { while(n--) { while(i % 2 != 0) sem_wait(&mutex); printf("Even : %d \n",i); i += 1; sem_post(&mutex); //sem_post(&OE); } } int main() { sem_init(&mutex, 0, 1); //sem_init(&OE, 0, 0); pthread_t t1,t2; pthread_create(&t1,NULL,odd,NULL); pthread_create(&t2,NULL,even,NULL); pthread_join(t1,NULL); pthread_join(t2,NULL); sem_destroy(&mutex); return 0; }
分析与解答
核心问题:忙等逻辑与信号量的误用
这段代码里的while(i % 2 == 0)和while(i % 2 != 0)是典型的忙等循环,结合信号量的使用逻辑完全错误,直接导致线程陷入死锁无法继续执行:
初始执行的死锁过程
- 程序启动时
i=0,mutex初始值为1。假设EVEN线程先抢占CPU:- EVEN线程进入循环,
i%2 !=0条件不成立,跳过忙等循环,打印Even:0,将i自增为1,调用sem_post(&mutex)释放信号量(此时mutex值回到1)。 - 接着EVEN线程进入下一次循环,执行
while(i%2 !=0),此时i=1条件成立,于是调用sem_wait(&mutex)——因为mutex当前值为1,成功获取后信号量减为0,线程再次检查条件,依然成立,继续调用sem_wait(&mutex),但此时mutex已为0,EVEN线程阻塞在这个调用上。
- EVEN线程进入循环,
- 再看ODD线程:如果它此时调度执行,检查
i%2 ==0条件不成立,于是调用sem_wait(&mutex),但mutex已经是0,ODD线程也会阻塞。 - 最终两个线程都卡在
sem_wait上,互相等待对方释放mutex,但谁都无法唤醒,彻底死锁。
- 程序启动时
双信号量版本的工作原理
新增的OE信号量起到了精准唤醒对应线程的作用,而非靠忙等+通用互斥锁。EVEN线程完成打印后,调用sem_post(&OE)直接唤醒等待的ODD线程;ODD线程处理完毕后再唤醒EVEN,线程间形成有序的唤醒链,不会出现同时阻塞的情况。单mutex版本的根本错误
代码试图用mutex保护i的访问,同时用忙等循环判断执行条件,两者逻辑冲突:- 忙等循环里反复调用
sem_wait,会导致线程在条件不满足时持续抢占并持有信号量,最终把信号量耗尽,导致其他线程无法获取。 - 这种写法完全误解了信号量的作用:信号量是用来同步线程执行顺序的,不是用来做条件判断的锁。
- 忙等循环里反复调用
修复方案(更合理的同步方式)
如果要实现交替打印,使用互斥锁+条件变量是更贴合场景的方案,它能让线程在条件不满足时主动释放锁并进入等待,直到被唤醒:
#include <stdio.h> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex; pthread_cond_t cond; int i=0, n=50; void* odd(void* arg) { while(n--) { pthread_mutex_lock(&mutex); // 等待i变为奇数,期间释放锁 while(i % 2 == 0) pthread_cond_wait(&cond, &mutex); printf("Odd : %d \n",i); i += 1; // 唤醒等待的偶数线程 pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); } } void* even(void* arg) { while(n--) { pthread_mutex_lock(&mutex); // 等待i变为偶数,期间释放锁 while(i % 2 != 0) pthread_cond_wait(&cond, &mutex); printf("Even : %d \n",i); i += 1; // 唤醒等待的奇数线程 pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); } } int main() { pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); pthread_t t1,t2; pthread_create(&t1,NULL,odd,NULL); pthread_create(&t2,NULL,even,NULL); pthread_join(t1,NULL); pthread_join(t2,NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0; }
这个实现中,线程不会无意义地忙等,只有在条件满足时才会执行打印操作,执行完毕后精准唤醒对方线程,完全避免了死锁问题。
内容的提问来源于stack exchange,提问作者Harsh Agarwal
相关产品推荐
相关产品推荐

