C语言线程中如何正常退出主进程?美发沙龙模拟程序问题
问题描述
我正在开发一个模拟美发沙龙的C程序,遇到了从simulation_timer_thread线程终止主进程的问题。目前只有调用kill(getpid(), SIGKILL)能成功退出主进程,但这不是理想方案。
在simulation_timer_thread中输出printf(RED "Symulacja zakończona można zakończyć program.\n" RESET);后,我尝试用exit(EXIT_SUCCESS)结束程序,却发现主进程仍在运行——通过strace排查,进程卡在等待输入的read(0)调用上。
但koniec函数和快速退出的信号处理程序能正常终止程序,我猜测是线程的结构特性导致exit()无法正常触发进程终止。试过pthread_cancel和pthread_join,也没能有效终止主进程,希望有人解释原因并提供更优处理方案。
附相关代码片段:
char menu = 0; while (menu != '3' /*&& !koniec_symulacji*/) { printf(CYAN "1 - Zakończ pracę fryzjera\n"); printf("2 - Natychmiastowo zamknij salon\n"); printf("3 - Zwolnij zasoby i zakończ program\n" RESET); while (getchar() != '\n') ; // 清空缓冲区 while (scanf("%c", &menu) != 1) ; switch (menu) { case '1': zabij_fryzjera(); // 发送信号1终止一名理发师 break; case '2': sig_handler_int(0); // 发送信号2立即终止所有客户 break; case '3': koniec(0); // 释放资源并终止程序 break; default: printf(RED "Niepoprawna opcja\n" RESET); break; } } // pthread_join(timer_thread, NULL); // return 0; } ... void *simulation_timer_thread(void *arg) { if (TP > 0) { sleep(TP); // TP为沙龙启动延迟时间,大于0则等待对应秒数 } printf(YELLOW "Salon otwarty.\n" RESET); // 输出沙龙启动提示 sem_setval(poczekalnia_semafor, K); // 初始化等候室信号量(K个空位) printf(YELLOW "Zainicjalizowano poczekalnię, ilość miejsc: %d.\n" RESET, sem_getval(poczekalnia_semafor)); // 输出等候室初始化信息 int remaining = sim_duration; // 模拟剩余时长 while (remaining > 0) // 模拟倒计时循环 { printf(MAGENTA "Czas pozostały: %d s\n", remaining); // 输出剩余时间 sleep(1); // 每秒递减一次 remaining--; } sem_setval(poczekalnia_semafor, 0); // 关闭等候室(新客户无法进入) printf(YELLOW "Salon zamknięty.\n" RESET); zabij_klientow(); zabij_fryzjerow(); zwolnij_zasoby_kierownik(); printf(RED "Symulacja zakończona można zakończyć program.\n" RESET); // koniec_symulacji = 1; // pthread_cancel(timer_thread); // pthread_join(timer_thread, NULL); // exit(EXIT_SUCCESS); // return NULL; // kill(getpid(), SIGKILL); } void zabij_klientow() { for (int i = 0; i < P; i++) // 终止所有客户进程 { kill(klienci[i], 2); // 发送SIGINT信号 } wait_for_process(P); } void zabij_fryzjerow() { for (int i = 0; i < F; i++) // 终止所有理发师进程 { kill(fryzjerzy[i], 1); // 发送SIGHUP信号 } wait_for_process(F); } void zwolnij_zasoby_kierownik() // 释放所有系统资源 { // 删除消息队列、信号量、共享内存 usun_kolejke_komunikatow(msg_qid); usun_semafor(kasa_semafor); usun_semafor(fotele_semafor); usun_semafor(poczekalnia_semafor); odlacz_pamiec_dzielona(kasa); usun_pamiec_dzielona(shm_id); printf(YELLOW "Zasoby zwolnione\n" RESET); }
原因分析
exit()未生效的核心原因:exit()是进程级终止调用,理论上会终止所有线程,但它会先执行进程级清理操作(刷新stdio缓冲区、调用atexit注册的清理函数)。如果你的主线程卡在read(0)(等待用户输入),且清理流程中某个步骤卡住(比如资源释放失败),就会导致进程无法及时终止。而SIGKILL是强制终止信号,跳过所有清理流程,因此能直接结束进程。pthread_cancel无效的原因:你之前调用的pthread_cancel(timer_thread)是取消当前线程,而非主线程,自然无法触发主进程退出。pthread_join的局限性:pthread_join仅用于等待目标线程结束,本身不具备终止线程或进程的能力,单独使用无法解决问题。
最优解决方案
方案1:全局标志+信号唤醒(最优雅)
通过全局原子变量通知主线程退出,并发送信号中断主线程的输入等待:
- 定义全局原子布尔变量:
_Atomic bool simulation_ended = false; - 注册自定义信号处理程序(仅用于中断系统调用):
void sig_handler_usr1(int signum) { // 空实现,仅中断阻塞的输入调用 } - 主函数中注册信号,并修改循环条件:
int main() { // 其他初始化代码... signal(SIGUSR1, sig_handler_usr1); char menu = 0; while (menu != '3' && !simulation_ended) { printf(CYAN "1 - Zakończ pracę fryzjera\n"); printf("2 - Natychmiastowo zamknij salon\n"); printf("3 - Zwolnij zasoby i zakończ program\n" RESET); // 处理缓冲区,同时检查终止标志 int c; while ((c = getchar()) != '\n' && c != EOF && !simulation_ended); if (simulation_ended) break; // 处理输入,同时检查终止标志 while (scanf("%c", &menu) != 1 && !simulation_ended); if (simulation_ended) break; // 原switch逻辑... } koniec(0); // 统一释放资源并退出 return 0; } - 在
simulation_timer_thread末尾添加:printf(RED "Symulacja zakończona można zakończyć program.\n" RESET); simulation_ended = true; kill(getpid(), SIGUSR1); // 发送信号中断主线程的输入等待 return NULL;
方案2:终止主线程
如果方案1不适用,可直接取消主线程:
- 主函数中保存主线程ID:
pthread_t main_thread = pthread_self(); - 在
simulation_timer_thread末尾调用:pthread_cancel(main_thread); - 确保主线程处于可取消状态(默认允许),且
scanf、getchar都是标准取消点,主线程会在等待输入时被终止。
方案3:使用_exit()强制终止(应急方案)
如果exit()因清理流程卡住,可跳过清理直接终止进程,但需确保已手动释放所有资源:
// 在simulation_timer_thread末尾调用 zwolnij_zasoby_kierownik(); // 先手动释放资源 printf(RED "Symulacja zakończona można zakończyć program.\n" RESET); _exit(EXIT_SUCCESS); // 跳过进程级清理,直接终止
内容的提问来源于stack exchange,提问作者Bartłomiej
相关产品推荐
相关产品推荐

