MST

星途 面试题库

面试题:C语言多线程信号处理的同步问题

假设你有一个多线程C程序,主线程创建了多个工作线程。主线程需要发送一个信号给所有工作线程,通知它们执行特定任务。请描述实现这个功能的详细步骤,并讨论如何确保线程间同步,避免竞争条件。
16.9万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试
  1. 实现步骤
    • 信号定义
      • 首先,在程序中定义一个全局变量来作为信号标志,例如:
        volatile sig_atomic_t signal_flag = 0;
        
        这里使用volatile sig_atomic_t类型是为了确保对这个变量的访问是原子操作,避免编译器优化导致的意外结果。
    • 工作线程
      • 每个工作线程在其循环中需要不断检查这个信号标志。例如:
        void* worker_thread(void* arg) {
            while (1) {
                // 检查信号标志
                if (signal_flag) {
                    // 执行特定任务
                    // 例如:
                    printf("Worker thread is doing the specific task.\n");
                    // 重置信号标志,如果需要重复使用信号机制,可以在这里重置
                    signal_flag = 0;
                }
                // 线程的其他工作,例如处理其他任务或者睡眠
                sleep(1);
            }
            return NULL;
        }
        
    • 主线程
      • 主线程在需要发送信号时,设置信号标志。例如:
        int main() {
            pthread_t threads[NUM_THREADS];
            // 创建工作线程
            for (int i = 0; i < NUM_THREADS; i++) {
                pthread_create(&threads[i], NULL, worker_thread, NULL);
            }
            // 主线程做其他工作
            sleep(5);
            // 发送信号
            signal_flag = 1;
            // 等待所有线程完成(这里只是示例,实际可能不需要等待所有线程完成特定任务后才退出)
            for (int i = 0; i < NUM_THREADS; i++) {
                pthread_join(threads[i], NULL);
            }
            return 0;
        }
        
  2. 线程间同步与避免竞争条件
    • 互斥锁(Mutex)
      • 可以使用互斥锁来保护对signal_flag的访问。在主线程设置信号标志和工作线程检查信号标志时,都需要先获取互斥锁。
      • 定义互斥锁:
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        
      • 在主线程发送信号时:
        pthread_mutex_lock(&mutex);
        signal_flag = 1;
        pthread_mutex_unlock(&mutex);
        
      • 在工作线程检查信号标志时:
        pthread_mutex_lock(&mutex);
        if (signal_flag) {
            // 执行特定任务
            signal_flag = 0;
        }
        pthread_mutex_unlock(&mutex);
        
    • 条件变量(Condition Variable)
      • 条件变量可以更高效地实现线程间的同步。主线程在设置信号标志后,可以通知条件变量,工作线程在等待信号时,可以等待条件变量。
      • 定义条件变量:
        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
        
      • 在主线程发送信号时:
        pthread_mutex_lock(&mutex);
        signal_flag = 1;
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mutex);
        
      • 在工作线程检查信号标志时:
        pthread_mutex_lock(&mutex);
        while (!signal_flag) {
            pthread_cond_wait(&cond, &mutex);
        }
        // 执行特定任务
        signal_flag = 0;
        pthread_mutex_unlock(&mutex);
        
    • 屏障(Barrier)
      • 如果主线程需要确保所有工作线程都收到信号并开始执行特定任务后再继续执行其他操作,可以使用屏障。例如:
      • 定义屏障:
        pthread_barrier_t barrier;
        pthread_barrier_init(&barrier, NULL, NUM_THREADS + 1);
        
      • 在工作线程中,当收到信号并开始执行特定任务前,调用屏障:
        pthread_mutex_lock(&mutex);
        if (signal_flag) {
            pthread_barrier_wait(&barrier);
            // 执行特定任务
            signal_flag = 0;
        }
        pthread_mutex_unlock(&mutex);
        
      • 在主线程发送信号后,也调用屏障:
        pthread_mutex_lock(&mutex);
        signal_flag = 1;
        pthread_mutex_unlock(&mutex);
        pthread_barrier_wait(&barrier);
        
    • 原子操作
      • 除了使用volatile sig_atomic_t类型确保对signal_flag的原子访问外,也可以使用平台特定的原子操作函数。例如在C11标准中,可以使用<stdatomic.h>头文件中的原子操作函数。例如atomic_storeatomic_load来设置和读取signal_flag,这样可以更细粒度地控制原子操作,并且在多平台上有更好的兼容性。例如:
        #include <stdatomic.h>
        atomic_int signal_flag = ATOMIC_VAR_INIT(0);
        // 主线程设置信号
        atomic_store(&signal_flag, 1);
        // 工作线程检查信号
        if (atomic_load(&signal_flag)) {
            // 执行任务
            atomic_store(&signal_flag, 0);
        }