You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单二元信号量实现奇偶线程交替打印失败问题咨询

问题

编写了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)是典型的忙等循环,结合信号量的使用逻辑完全错误,直接导致线程陷入死锁无法继续执行:

  1. 初始执行的死锁过程

    • 程序启动时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线程阻塞在这个调用上。
    • 再看ODD线程:如果它此时调度执行,检查i%2 ==0条件不成立,于是调用sem_wait(&mutex),但mutex已经是0,ODD线程也会阻塞。
    • 最终两个线程都卡在sem_wait上,互相等待对方释放mutex,但谁都无法唤醒,彻底死锁。
  2. 双信号量版本的工作原理
    新增的OE信号量起到了精准唤醒对应线程的作用,而非靠忙等+通用互斥锁。EVEN线程完成打印后,调用sem_post(&OE)直接唤醒等待的ODD线程;ODD线程处理完毕后再唤醒EVEN,线程间形成有序的唤醒链,不会出现同时阻塞的情况。

  3. 单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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 03:09:59