C++多线程编程:如何在服务员线程侧判断所有顾客线程已全部结束
问题根因
你遇到的永久阻塞问题根源是顾客存活状态检查和**callWaiter信号量等待**是两个非原子操作,两者的间隙中最后一批顾客可能已经全部执行完毕,导致服务员进入等待后再也没有新的信号唤醒。
推荐解决方案
优先推荐毒药信号法,无额外轮询开销,逻辑严谨,不会出现竞态:
实现步骤
- 新增3个共享变量:
int active_customers,初始值设为40(你的顾客总数量),记录未完成服务的顾客数pthread_mutex_t customer_lock,用于保护active_customers的读写原子性- 信号量
callWaiter初始值设为0
- 调整顾客线程逻辑,离开时更新活跃顾客计数:
void *customer(void * vargp){ // 原有业务逻辑不变 sem_post(&callWaiter); sem_wait(&waiterResponse); // 新增:离开时原子减少活跃顾客计数 pthread_mutex_lock(&customer_lock); active_customers--; pthread_mutex_unlock(&customer_lock); return nullptr; }
- 调整服务员线程逻辑,拿到信号后先判断是否需要退出:
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; }
- 主线程补充逻辑,所有顾客结束后给服务员发退出信号:
// 原有逻辑:初始化信号量、锁,启动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
相关产品推荐
相关产品推荐

