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

Linux环境下多线程semaphore同步打印死锁排查及扩展实现咨询

解决多线程信号量同步死锁问题及扩展实现

咱们先拆解你代码里死锁的根源,再给出修复方案,最后聊聊怎么扩展到4线程的场景。

一、死锁原因分析

你想要实现3→4→5循环打印的逻辑,但代码里的信号量设计完全偏离了这个目标,直接导致了死锁或者线程调度混乱:

  1. 多信号量争夺逻辑错误:每个线程都要同时抢两个信号量(比如打印3的线程要拿sem1和sem3,打印4、5的线程都要抢sem2和sem3),这种设计不仅打乱了执行顺序,还会出现“线程A持有信号量X、等信号量Y;线程B持有信号量Y、等信号量X”的死锁场景。
  2. 信号量释放逻辑混乱:线程执行完后同时释放两个信号量,会让信号量计数不断累加,彻底破坏同步规则,导致线程调度完全失控。

二、修复后的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线程完全一致,只是新增一个信号量,延长接力链即可:

  1. 新增信号量:添加sem_print6控制打印6的线程。
  2. 调整接力顺序:
    • 打印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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:46:40