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

C++多线程编程:如何在服务员线程侧判断所有顾客线程已全部结束

问题根因

你遇到的永久阻塞问题根源是顾客存活状态检查和**callWaiter信号量等待**是两个非原子操作,两者的间隙中最后一批顾客可能已经全部执行完毕,导致服务员进入等待后再也没有新的信号唤醒。

推荐解决方案

优先推荐毒药信号法,无额外轮询开销,逻辑严谨,不会出现竞态:

实现步骤

  1. 新增3个共享变量:
  • int active_customers,初始值设为40(你的顾客总数量),记录未完成服务的顾客数
  • pthread_mutex_t customer_lock,用于保护active_customers的读写原子性
  • 信号量callWaiter初始值设为0
  1. 调整顾客线程逻辑,离开时更新活跃顾客计数:
void *customer(void * vargp){
    // 原有业务逻辑不变
    sem_post(&callWaiter);
    sem_wait(&waiterResponse);
    
    // 新增:离开时原子减少活跃顾客计数
    pthread_mutex_lock(&customer_lock);
    active_customers--;
    pthread_mutex_unlock(&customer_lock);
    
    return nullptr;
}
  1. 调整服务员线程逻辑,拿到信号后先判断是否需要退出:
void *waiter(void *vargp){
    while(true) {
        sem_wait(&callWaiter);
        
        // 拿到信号后先判断是否还有未服务的顾客
        pthread_mutex_lock(&customer_lock);
        if (active_customers <= 0) {
            pthread_mutex_unlock(&customer_lock);
            break; // 没有顾客直接退出
        }
        pthread_mutex_unlock(&customer_lock);
        
        sem_post(&waiterResponse);
    }
    return nullptr;
}
  1. 主线程补充逻辑,所有顾客结束后给服务员发退出信号:
// 原有逻辑:初始化信号量、锁,启动40个顾客线程、3个服务员线程
// 新增:等待所有顾客线程执行完毕
for (int i = 0; i < 40; i++) {
    pthread_join(customer_thread_arr[i], nullptr);
}
// 发送和服务员数量相等的信号,唤醒所有阻塞的服务员
for (int i = 0; i < 3; i++) {
    sem_post(&callWaiter);
}
// 等待所有服务员线程退出后再销毁资源
for (int i = 0; i < 3; i++) {
    pthread_join(waiter_thread_arr[i], nullptr);
}

方案合理性说明

所有顾客线程退出后,active_customers肯定为0,此时主线程发送的信号会叫醒每个阻塞的服务员,服务员拿到信号后先判断active_customers为0直接退出,完全避免了状态检查和等待之间的竞态问题,也没有额外的CPU开销。

替代方案:超时等待法

如果你不想修改主线程逻辑,可以用定时等待信号量的方式避免永久阻塞,代码实现如下:

void *waiter(void *vargp){
    while(true) {
        // 先判断是否还有活跃顾客
        pthread_mutex_lock(&customer_lock);
        if (active_customers <= 0) {
            pthread_mutex_unlock(&customer_lock);
            break;
        }
        pthread_mutex_unlock(&customer_lock);
        
        // 设置100ms超时等待信号量
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        ts.tv_nsec += 100 * 1000 * 1000;
        if (ts.tv_nsec >= 1000000000) {
            ts.tv_sec += 1;
            ts.tv_nsec -= 1000000000;
        }
        // 超时后回到循环开头重新判断顾客状态
        if (sem_timedwait(&callWaiter, &ts) == 0) {
            sem_post(&waiterResponse);
        }
    }
    return nullptr;
}

该方案会有极少量的轮询开销,适合不允许修改主线程逻辑的场景。


内容的提问来源于stack exchange,提问作者GildedTruth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 15:39:04