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

使用pthread_cond_signal与pthread_cond_timedwait的线程通信超时问题

问题:双线程信号交互程序频繁触发超时终止

我编写了一个包含两个互相发送信号的线程的程序,初始运行符合预期,但多次运行后会因pthread_cond_timedwait触发超时而终止。测试中设置一旦某个线程触发超时就停止程序,期望程序能一直运行直到手动终止,请问该如何解决?

程序代码

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

pthread_mutex_t mutex1;
pthread_cond_t cond1;
pthread_mutex_t mutex2;
pthread_cond_t cond2;
bool run = true;

void signal_handler(int sig_num) { run = false; }

void* thread1(void* arg) {
    struct timespec ts;
    struct timeval now;
    uint32_t runs = 0;

    while (run) {
        pthread_mutex_lock(&mutex1);

        gettimeofday(&now, NULL);
        ts.tv_sec = now.tv_sec + 1;
        ts.tv_nsec = now.tv_usec * 1000;
        
        pthread_mutex_unlock(&mutex1);
        pthread_mutex_lock(&mutex2);
        int result = pthread_cond_timedwait(&cond2, &mutex2, &ts);
        if (result == ETIMEDOUT) {
            printf("Thread 1: Wait timed out\n");
            run = false;
        } else if (result == 0) {
            //  printf("Thread 1: Signal received from Thread 2\n");
            if (pthread_cond_signal(&cond1) != 0) {
                printf("Failed to signal cond1\n");
            }
            runs++;
        }
        pthread_mutex_unlock(&mutex2);
    }

    printf("T1: runs=%d\n", runs);
    return NULL;
}

void* thread2(void* arg) {
    struct timespec ts;
    struct timeval now;
    uint32_t runs = 0;

    while (run) {
        pthread_mutex_lock(&mutex2);

        gettimeofday(&now, NULL);
        ts.tv_sec = now.tv_sec + 1;
        ts.tv_nsec = now.tv_usec * 1000;

        // printf("Thread 2: Sending signal to Thread 1\n");
        if (pthread_cond_signal(&cond2) != 0) {
            printf("Failed to signal cond2\n");
        }
        pthread_mutex_unlock(&mutex2);

        pthread_mutex_lock(&mutex1);
        int result = pthread_cond_timedwait(&cond1, &mutex1, &ts);
        if (result == ETIMEDOUT) {
            printf("Thread 2: Wait timed out\n");
            run = false;
        } else {
            runs++;
        }
        pthread_mutex_unlock(&mutex1);
    }

    printf("T2: runs=%d\n", runs);

    return NULL;
}

int main() {
    pthread_t tid1, tid2;

    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    pthread_mutex_init(&mutex1, NULL);
    pthread_cond_init(&cond1, NULL);
    pthread_mutex_init(&mutex2, NULL);
    pthread_cond_init(&cond2, NULL);

    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_cond_destroy(&cond1);
    pthread_mutex_destroy(&mutex2);
    pthread_cond_destroy(&cond2);

    return 0;
}

运行输出示例

$ /tmp/test 
Thread 2: Wait timed out
T2: runs=84498
Thread 1: Wait timed out
T1: runs=84499

$ /tmp/test
Thread 2: Wait timed out
T2: runs=148
Thread 1: Wait timed out
T1: runs=148

问题原因与解决办法

核心问题

  1. 超时时间提前计算导致过期:超时时间在循环早期就计算好了,但线程切换、锁竞争会消耗时间,等实际调用pthread_cond_timedwait时,这个1秒的超时窗口可能已经过了,直接触发超时。
  2. 条件变量信号丢失:线程2先发送cond2信号,再等待cond1,但此时线程1可能还没进入cond2的等待状态,信号直接被丢弃。后续线程1进入等待时,没有信号触发,最终超时。
  3. 未遵循条件变量的正确使用范式:条件变量应该和共享状态标记配合使用,单独发送信号很容易出现丢信号的情况。

修复后的代码

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

pthread_mutex_t mutex1;
pthread_cond_t cond1;
bool cond1_signaled = false; // 配合cond1的状态标记

pthread_mutex_t mutex2;
pthread_cond_t cond2;
bool cond2_signaled = false; // 配合cond2的状态标记

bool run = true;

void signal_handler(int sig_num) { run = false; }

void* thread1(void* arg) {
    struct timespec ts;
    struct timeval now;
    uint32_t runs = 0;

    while (run) {
        pthread_mutex_lock(&mutex2);
        
        // 先检查是否已有信号,避免丢信号
        while (!cond2_signaled && run) {
            // 等待前才计算超时时间,确保准确性
            gettimeofday(&now, NULL);
            ts.tv_sec = now.tv_sec + 1;
            ts.tv_nsec = now.tv_usec * 1000;
            
            int result = pthread_cond_timedwait(&cond2, &mutex2, &ts);
            if (result == ETIMEDOUT) {
                printf("Thread 1: Wait timed out\n");
                run = false;
                break;
            }
            // 虚假唤醒时重新检查状态
        }
        
        if (cond2_signaled && run) {
            cond2_signaled = false; // 重置状态
            // 发送信号给线程2
            pthread_mutex_lock(&mutex1);
            cond1_signaled = true;
            pthread_cond_signal(&cond1);
            pthread_mutex_unlock(&mutex1);
            runs++;
        }
        pthread_mutex_unlock(&mutex2);
    }

    printf("T1: runs=%d\n", runs);
    return NULL;
}

void* thread2(void* arg) {
    struct timespec ts;
    struct timeval now;
    uint32_t runs = 0;

    while (run) {
        // 发送信号给线程1
        pthread_mutex_lock(&mutex2);
        cond2_signaled = true;
        pthread_cond_signal(&cond2);
        pthread_mutex_unlock(&mutex2);
        
        pthread_mutex_lock(&mutex1);
        // 检查状态,等待信号
        while (!cond1_signaled && run) {
            gettimeofday(&now, NULL);
            ts.tv_sec = now.tv_sec + 1;
            ts.tv_nsec = now.tv_usec * 1000;
            
            int result = pthread_cond_timedwait(&cond1, &mutex1, &ts);
            if (result == ETIMEDOUT) {
                printf("Thread 2: Wait timed out\n");
                run = false;
                break;
            }
        }
        
        if (cond1_signaled && run) {
            cond1_signaled = false;
            runs++;
        }
        pthread_mutex_unlock(&mutex1);
    }

    printf("T2: runs=%d\n", runs);
    return NULL;
}

int main() {
    pthread_t tid1, tid2;

    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    pthread_mutex_init(&mutex1, NULL);
    pthread_cond_init(&cond1, NULL);
    pthread_mutex_init(&mutex2, NULL);
    pthread_cond_init(&cond2, NULL);

    pthread_create(&tid1, NULL, thread1, NULL);
    pthread_create(&tid2, NULL, thread2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_cond_destroy(&cond1);
    pthread_mutex_destroy(&mutex2);
    pthread_cond_destroy(&cond2);

    return 0;
}

关键改动说明

  • 增加状态标记:为每个条件变量添加condX_signaled布尔值,用来记录是否有未处理的信号,彻底解决信号丢失问题。
  • 延迟计算超时时间:把gettimeofday移到pthread_cond_timedwait调用的前一刻,确保超时时间是当前时间+1秒,避免提前计算导致的超时过期。
  • 使用while循环等待:处理条件变量的虚假唤醒问题,每次被唤醒后重新检查状态标记,确保是有效信号触发的唤醒。
  • 原子更新状态:修改状态标记时必须持有对应的互斥锁,保证线程间的状态可见性。

这样修改后,程序可以稳定运行直到手动用Ctrl+C终止,不会再出现无故超时的情况。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 09:22:02