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

C语言pthread多线程:通过ncurses按钮实现线程挂起与恢复

实现带ncurses按钮控制的多线程协同程序(pthread+条件变量)

我明白你要做的是一个结合多线程协同、终端UI控制的C程序——用pthread创建5个工作线程,通过ncurses按钮手动挂起/恢复,还有一个协调线程用条件变量阻塞它们直到满足条件。这个需求挺实用的,我来给你拆解实现思路,再附上完整的可运行代码:

整体架构梳理

先明确几个核心组件的分工,避免逻辑混乱:

  • 5个工作线程:执行实际业务任务,支持手动挂起/恢复,启动后会先被协调线程阻塞,直到条件满足才开始运行。
  • 协调线程:负责监控"有效条件"(比如外部事件、计时完成等),条件满足后通过条件变量唤醒所有工作线程。
  • ncurses UI线程:独立绘制终端按钮,监听用户点击事件,实时切换对应工作线程的挂起/恢复状态。

关键技术点实现细节

1. 线程挂起/恢复的安全实现

⚠️ 注意:不要使用已废弃的pthread_suspend()和pthread_resume(),这些函数容易导致死锁和资源泄漏。我们采用互斥锁+条件变量+状态标记的安全方案:

  • 给每个工作线程维护一个bool suspend_flag状态标记。
  • 工作线程在任务循环中检查标记,若为true则调用pthread_cond_wait()进入等待状态。
  • 用户点击按钮时,切换对应线程的标记,并调用pthread_cond_signal()唤醒线程。

2. 协调线程的条件变量逻辑

协调线程的核心是用条件变量实现批量阻塞/唤醒:

  • 定义全局条件变量cond和互斥锁cond_mutex,配合condition_met标记判断条件是否满足。
  • 工作线程启动后,先获取互斥锁,通过while(!condition_met)循环调用pthread_cond_wait()阻塞,直到协调线程触发条件。
  • 协调线程满足条件时,更新condition_met为true,调用pthread_cond_broadcast()唤醒所有等待的工作线程。

3. ncurses按钮交互实现

ncurses部分需要注意终端环境的初始化和事件监听:

  • 初始化ncurses时要调用cbreak()、noecho()、keypad(stdscr, TRUE)等函数,确保键盘事件能被正确捕获。
  • 每个工作线程对应一个按钮,根据线程状态动态显示"挂起"或"恢复"文本。
  • 监听键盘输入,判断用户点击的按钮编号,实时更新对应线程的挂起状态。

完整代码示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <ncurses.h>
#include <unistd.h>
#include <stdbool.h>

#define NUM_THREADS 5

// 工作线程结构体:保存线程ID、挂起状态和索引
typedef struct {
    pthread_t tid;
    bool suspend_flag;
    int index;
} WorkerThread;

WorkerThread workers[NUM_THREADS];

// 协调线程用的条件变量和互斥锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER;
bool condition_met = false;

// 线程挂起/恢复控制用的互斥锁和条件变量(每个线程独立一套)
pthread_mutex_t suspend_mutex[NUM_THREADS];
pthread_cond_t suspend_cond[NUM_THREADS];

// 工作线程执行函数
void* worker_func(void* arg) {
    WorkerThread* worker = (WorkerThread*)arg;
    int thread_idx = worker->index;

    // 第一步:等待协调线程的条件满足
    pthread_mutex_lock(&cond_mutex);
    while (!condition_met) {
        pthread_cond_wait(&cond, &cond_mutex);
    }
    pthread_mutex_unlock(&cond_mutex);

    // 第二步:进入任务循环,支持挂起/恢复
    while (1) {
        // 检查是否需要挂起
        pthread_mutex_lock(&suspend_mutex[thread_idx]);
        while (worker->suspend_flag) {
            pthread_cond_wait(&suspend_cond[thread_idx], &suspend_mutex[thread_idx]);
        }
        pthread_mutex_unlock(&suspend_mutex[thread_idx]);

        // 模拟业务任务:打印线程状态
        printf("线程%d正在执行任务...\n", thread_idx);
        fflush(stdout); // 兼容ncurses的终端输出设置
        sleep(1);
    }
    return NULL;
}

// 协调线程:模拟条件满足的过程
void* coordinator_func(void* arg) {
    // 模拟等待条件(比如等待5秒,或监听外部事件)
    printf("协调线程:等待有效条件满足...\n");
    sleep(5);

    // 条件满足,唤醒所有工作线程
    pthread_mutex_lock(&cond_mutex);
    condition_met = true;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&cond_mutex);
    printf("协调线程:条件已满足,所有工作线程开始运行!\n");

    return NULL;
}

// ncurses UI线程:绘制按钮+处理用户交互
void* ui_func(void* arg) {
    // 初始化ncurses环境
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    curs_set(0); // 隐藏光标

    int y_start = 2;
    int x_start = 2;
    char* button_labels[NUM_THREADS];
    // 初始化按钮文本
    for (int i = 0; i < NUM_THREADS; i++) {
        button_labels[i] = "挂起";
    }

    while (1) {
        // 刷新UI
        clear();
        mvprintw(0, 0, "点击数字1-%d控制对应线程,按q退出程序", NUM_THREADS);
        for (int i = 0; i < NUM_THREADS; i++) {
            mvprintw(y_start + i, x_start, "[%d] %s", i+1, button_labels[i]);
            mvchgat(y_start + i, x_start, 12, A_REVERSE, 0, NULL); // 高亮按钮
        }
        refresh();

        // 处理键盘事件
        int ch = getch();
        if (ch == 'q') {
            break;
        } else if (ch >= '1' && ch <= '0' + NUM_THREADS) {
            int thread_idx = ch - '1';
            // 切换线程挂起状态
            pthread_mutex_lock(&suspend_mutex[thread_idx]);
            workers[thread_idx].suspend_flag = !workers[thread_idx].suspend_flag;
            if (workers[thread_idx].suspend_flag) {
                button_labels[thread_idx] = "恢复";
            } else {
                button_labels[thread_idx] = "挂起";
                pthread_cond_signal(&suspend_cond[thread_idx]); // 唤醒线程
            }
            pthread_mutex_unlock(&suspend_mutex[thread_idx]);
        }
    }

    endwin(); // 关闭ncurses
    exit(0); // 优雅退出程序
}

int main() {
    pthread_t coordinator_tid;
    pthread_t ui_tid;

    // 初始化挂起控制的锁和条件变量
    for (int i = 0; i < NUM_THREADS; i++) {
        workers[i].index = i;
        workers[i].suspend_flag = false;
        pthread_mutex_init(&suspend_mutex[i], NULL);
        pthread_cond_init(&suspend_cond[i], NULL);
    }

    // 创建各线程
    pthread_create(&coordinator_tid, NULL, coordinator_func, NULL);
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&workers[i].tid, NULL, worker_func, &workers[i]);
    }
    pthread_create(&ui_tid, NULL, ui_func, NULL);

    // 等待线程结束(实际可添加信号处理实现优雅终止)
    pthread_join(coordinator_tid, NULL);
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(workers[i].tid, NULL);
    }
    pthread_join(ui_tid, NULL);

    // 清理资源
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&cond_mutex);
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_mutex_destroy(&suspend_mutex[i]);
        pthread_cond_destroy(&suspend_cond[i]);
    }

    return 0;
}

代码编译与运行

编译时需要链接pthread和ncurses库:

gcc -o thread_control thread_control.c -lpthread -lncurses

运行后,协调线程会等待5秒,之后所有工作线程开始运行;点击数字1-5可以切换对应线程的挂起/恢复状态,按q退出程序。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:05:59