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

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);
}
原因分析
  1. exit()未生效的核心原因:exit()是进程级终止调用,理论上会终止所有线程,但它会先执行进程级清理操作(刷新stdio缓冲区、调用atexit注册的清理函数)。如果你的主线程卡在read(0)(等待用户输入),且清理流程中某个步骤卡住(比如资源释放失败),就会导致进程无法及时终止。而SIGKILL是强制终止信号,跳过所有清理流程,因此能直接结束进程。
  2. pthread_cancel无效的原因:你之前调用的pthread_cancel(timer_thread)是取消当前线程,而非主线程,自然无法触发主进程退出。
  3. pthread_join的局限性:pthread_join仅用于等待目标线程结束,本身不具备终止线程或进程的能力,单独使用无法解决问题。
最优解决方案

方案1:全局标志+信号唤醒(最优雅)

通过全局原子变量通知主线程退出,并发送信号中断主线程的输入等待:

  1. 定义全局原子布尔变量:_Atomic bool simulation_ended = false;
  2. 注册自定义信号处理程序(仅用于中断系统调用):
    void sig_handler_usr1(int signum) {
        // 空实现,仅中断阻塞的输入调用
    }
    
  3. 主函数中注册信号,并修改循环条件:
    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;
    }
    
  4. 在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不适用,可直接取消主线程:

  1. 主函数中保存主线程ID:pthread_t main_thread = pthread_self();
  2. 在simulation_timer_thread末尾调用:pthread_cancel(main_thread);
  3. 确保主线程处于可取消状态(默认允许),且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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 03:59:55