pthread_cond_signal() 没有给信号线程足够的时间运行

Posted

技术标签:

【中文标题】pthread_cond_signal() 没有给信号线程足够的时间运行【英文标题】:pthread_cond_signal() not giving enough time for the signaled thread to run 【发布时间】:2019-04-09 22:29:05 【问题描述】:

调用 pthread_cond_signal 的线程在释放信号线程之前重新获取互斥锁。

下面的代码显示了当前问题的一个简单示例。主线程将持有锁,创建工作线程,然后进入一个在数据进入时打印数据的循环。它通过条件变量发出运行信号。

工作线程将进入一个循环,生成数据,获取锁,写入数据,然后向主线程发出信号。

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;

void *thread(void *arg) 
    int length = *(int *) arg;
    for (int i = 0; i < length; i++) 
        // Do some work
        pthread_mutex_lock(&lock);
        data = i;
        fprintf(stdout, "Writing\n");
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    
    pthread_exit(0);



int main(int argc, const char *argv[]) 
    pthread_t worker;
    int length = 4;

    pthread_mutex_lock(&lock);
    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) 
        fprintf(stdout, "Waiting\n");
        pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
    
    pthread_mutex_unlock(&lock);

    pthread_join(worker, NULL);
    return 0;

这将给出以下输出:

Waiting
Writing
Writing
Writing
Writing
read data: 3
Waiting

预期行为: 主线程持有互斥锁,只有在等待时才释放它。然后,工作线程将写入其数据并向主线程发出信号。主线程将立即锁定互斥量 on 信号,然后读取数据并返回等待,释放互斥量。与此同时,工作线程将完成它的工作并一直等待,直到主线程再次等待写入其数据并发出信号。

相反,工作线程似乎在调用信号后立即获得互斥锁,很少让主线程获得访问权限。如果我在工作线程中放置一个睡眠来代替// Do some work,那么它将给出预期的输出。

【问题讨论】:

为什么会出乎意料? @immibis 由于主线程正在等待 cond,我预计它会在收到信号后立即停止被阻塞并拥有互斥锁的优先权。相反,工作线程能够在主线程之前到达互斥锁。 你应该使用sched_yield()而不是睡觉。 @Cyrusc 显然,与工作线程再次获取互斥锁相比,解除阻塞主线程所需的时间更长。当事情是多线程的时,您无法选择它们发生的顺序! 【参考方案1】:

发信号通知条件变量不会为将互斥锁锁定到正在等待该条件变量的线程提供任何优先级。这意味着至少有一个等待条件变量的线程将开始尝试获取互斥锁,以便它可以从pthread_cond_wait() 返回。如您所见,信令线程将继续执行,并且可以轻松地首先重新获取互斥锁。

在您等待的某些共享状态上,您永远不应该在没有实际条件的情况下使用条件变量 - 从pthread_cond_wait() 返回并不意味着线程肯定应该继续,这意味着它应该检查条件是否它等待的是真的。这就是为什么它们被称为条件变量

在这种情况下,你的写线程想要等待的状态是“主线程已经消费了我写的最后一个数据。”。但是,您的读取(主)线程需要等待一个条件 - “写入线程已写入一些新数据”。您可以使用一个标志变量来实现这两个条件,该变量指示一些新的、未使用的数据已写入data 变量。该标志开始时未设置,在更新data 时由写入线程设置,并在从data 读取时由主线程取消设置。写线程等待标志被取消,读线程等待标志被设置。

通过这种安排,您也不需要在启动写入线程时锁定互斥锁 - 线程启动的顺序无关紧要,因为无论哪种方式,一切都是一致的。

更新后的代码如下:

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int data = 0;
int data_available = 0;

void *thread(void *arg)

    int length = *(int *) arg;
    for (int i = 0; i < length; i++) 
        // Do some work
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to write\n");
        while (data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "Writing\n");
        data = i;
        data_available = 1;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    
    pthread_exit(0);



int main(int argc, const char *argv[])

    pthread_t worker;
    int length = 4;

    pthread_create(&worker, 0, thread, &length);

    for (int i = 0; i < length; i++) 
        pthread_mutex_lock(&lock);
        fprintf(stdout, "Waiting to read\n");
        while (!data_available)
            pthread_cond_wait(&cond, &lock);
        fprintf(stdout, "read data: %d\n", data);
        data_available = 0;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&lock);
    

    pthread_join(worker, NULL);
    return 0;

当然,线程最终会步调一致地工作 - 但基本上你有一个最大队列长度为 1 的生产者-消费者,所以这是意料之中的。

【讨论】:

以上是关于pthread_cond_signal() 没有给信号线程足够的时间运行的主要内容,如果未能解决你的问题,请参考以下文章

使用 pthread_cond_signal 的优雅线程终止证明有问题

如果 pthread_cond_signal 是线程中的最后一次调用,是不是存在数据竞争?

pthread_cond_wait 丢失来自 pthread_cond_signal 的信号

条件变脸pthread_cond_signal丢失问题

perfreduce pthread_cond_signal via wait counter

pthread_cond_wait 和 pthread_cond_signal 的性能