Linux环境下多线程semaphore同步打印死锁排查及扩展实现咨询
解决多线程信号量同步死锁问题及扩展实现
咱们先拆解你代码里死锁的根源,再给出修复方案,最后聊聊怎么扩展到4线程的场景。
一、死锁原因分析
你想要实现3→4→5循环打印的逻辑,但代码里的信号量设计完全偏离了这个目标,直接导致了死锁或者线程调度混乱:
- 多信号量争夺逻辑错误:每个线程都要同时抢两个信号量(比如打印3的线程要拿
sem1和sem3,打印4、5的线程都要抢sem2和sem3),这种设计不仅打乱了执行顺序,还会出现“线程A持有信号量X、等信号量Y;线程B持有信号量Y、等信号量X”的死锁场景。 - 信号量释放逻辑混乱:线程执行完后同时释放两个信号量,会让信号量计数不断累加,彻底破坏同步规则,导致线程调度完全失控。
二、修复后的3线程代码(依次打印3、4、5)
正确的思路是做接力式环形同步:每个线程只等待前一个线程的“允许信号”,执行完自己的任务后,给下一个线程发“允许信号”,形成一个闭环。这样每个线程只操作一个信号量,完全避免多信号量争夺的死锁风险:
#include <iostream> #include <pthread.h> #include <semaphore.h> using namespace std; // 三个信号量分别控制对应线程的执行 sem_t sem_print3; // 控制打印3的线程 sem_t sem_print4; // 控制打印4的线程 sem_t sem_print5; // 控制打印5的线程 void* print_3(void*) { for (int i = 0; i < 50; i++) { sem_wait(&sem_print3); // 等允许打印3的信号 cout << "3"; sem_post(&sem_print4); // 告诉打印4的线程可以执行了 } return nullptr; } void* print_4(void*) { for (int i = 0; i < 50; i++) { sem_wait(&sem_print4); // 等允许打印4的信号 cout << "4"; sem_post(&sem_print5); // 告诉打印5的线程可以执行了 } return nullptr; } void* print_5(void*) { for (int i = 0; i < 50; i++) { sem_wait(&sem_print5); // 等允许打印5的信号 cout << "5"; sem_post(&sem_print3); // 回到打印3的线程,完成循环 } return nullptr; } int main() { pthread_t t3, t4, t5; // 初始化:让打印3的线程先启动,其余线程初始阻塞 sem_init(&sem_print3, 0, 1); sem_init(&sem_print4, 0, 0); sem_init(&sem_print5, 0, 0); pthread_create(&t3, NULL, print_3, NULL); pthread_create(&t4, NULL, print_4, NULL); pthread_create(&t5, NULL, print_5, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_join(t5, NULL); // 销毁信号量,避免资源泄漏 sem_destroy(&sem_print3); sem_destroy(&sem_print4); sem_destroy(&sem_print5); cout << endl; // 最后换行,让输出更整洁 return 0; }
修复核心说明:
- 接力式闭环:就像接力赛,每个线程完成任务后把“接力棒”传给下一个,最后一个再传回第一个,完美实现3→4→5→3的循环顺序。
- 单信号量操作:每个线程只获取一个信号量、释放一个信号量,彻底杜绝了多信号量争夺导致的死锁。
- 合理初始化:
sem_print3初始值设为1,让第一个线程直接启动,其余信号量初始为0,保证初始状态下只有打印3的线程能执行。
三、扩展到4线程依次打印3、4、5、6的实现建议
扩展逻辑和3线程完全一致,只是新增一个信号量,延长接力链即可:
- 新增信号量:添加
sem_print6控制打印6的线程。 - 调整接力顺序:
- 打印3的线程:执行完释放
sem_print4 - 打印4的线程:执行完释放
sem_print5 - 打印5的线程:执行完释放
sem_print6 - 打印6的线程:执行完释放
sem_print3(形成新的闭环)
- 打印3的线程:执行完释放
- 初始化信号量:依然让第一个线程(打印3)的信号量初始值为1,其余为0。
示例代码片段:
// 新增控制打印6的信号量 sem_t sem_print6; void* print_6(void*) { for (int i = 0; i < 50; i++) { sem_wait(&sem_print6); cout << "6"; sem_post(&sem_print3); // 传回打印3的线程,完成循环 } return nullptr; } int main() { pthread_t t6; // 初始化新增的信号量 sem_init(&sem_print6, 0, 0); pthread_create(&t6, NULL, print_6, NULL); pthread_join(t6, NULL); sem_destroy(&sem_print6); // 其余逻辑和3线程版本一致 }
扩展注意事项:
- 始终保持每个线程只操作一个信号量的原则,避免多信号量获取带来的死锁风险。
- 接力顺序必须严格对应打印顺序,不能出错。
- 程序结束时记得销毁所有信号量,避免系统资源泄漏。
内容的提问来源于stack exchange,提问作者Ayush Gupta
相关产品推荐
相关产品推荐

