C语言pthread多线程使用二进制信号量仅单线程可获取问题咨询
首先需要先指出你示例代码中的笔误:main函数里的sem_init(&sem.ctx, 0, 1);拼写错误,正确写法为sem_init(&ctx.sem, 0, 1);,否则代码无法正常编译,属于编写示例时的手误。
问题核心原因
这个现象的本质是POSIX 信号量默认采用非公平实现,结合线程调度的上下文开销导致了单线程饥饿,具体逻辑如下:
- Linux 原生的 POSIX 信号量没有公平性强制要求,不会按照线程等待锁的先后顺序分配信号量,允许正在 CPU 上运行的线程在释放信号量后立刻"插队"重新获取,不需要优先给等待队列里的阻塞线程分配。
- 你的代码中
sleep(1)是在信号量持有区间(sem_wait和sem_post之间)执行,这1秒内其余9个线程都会阻塞在sem_wait调用上,进入内核的休眠等待队列。 - 当持有信号量的线程执行完
sleep、打印、计数器自增操作,调用sem_post释放信号量后,会立刻进入下一轮循环的sem_wait调用:此时信号量值刚被置为1,当前线程正处于运行态,仅需要一次用户态的原子操作就能拿到信号量;而等待队列里的阻塞线程从内核休眠态唤醒、完成上下文切换需要几百纳秒到几微秒的开销,完全赶不上当前线程的"插队"速度,所以永远抢不到信号量。
为什么删除sleep后表现正常
删除sleep后,信号量持有区间的逻辑只剩下计数器判断、printf打印和自增操作,其中printf是会触发用户态到内核态切换的系统调用,执行开销足够大:
- 要么
printf的执行消耗完了当前线程的时间片,线程在sem_post之后被内核调度切走,给其他等待线程让出了CPU; - 要么
printf的执行开销足够让等待队列里的其他线程完成唤醒、上下文切换,在当前线程下一轮调用sem_wait之前就抢到了信号量,因此呈现出线程调度随机的预期表现。
修复方案
如果需要保证多线程能轮流获取信号量,可以选择两种方案:
- 改用带公平性保证的同步原语,比如设置了
PTHREAD_MUTEX_FAIR_NP属性的互斥锁(Linux 特有,非 POSIX 标准) - 在
sem_post之后、下一轮sem_wait之前调用sched_yield()主动让出CPU,给其他等待线程运行的机会。
内容的提问来源于stack exchange,提问作者Daniel Walker
相关产品推荐
相关产品推荐

