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

Is there a data race if pthread_cond_signal is the last call in a thread?

-fsanitize=thread 编译它会产生 pthread_cond_signalpthread_cond_destroy 之间的数据竞争报告:

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

bool done = false;

pthread_mutex_t mutex;
pthread_cond_t cond;

void *func(void *)
{   
    pthread_mutex_lock(&mutex);

    done = true;

    pthread_mutex_unlock(&mutex);

    pthread_cond_signal(&cond);

    return NULL;
}

int main()
{
    pthread_t thread;

    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    if (pthread_create(&thread, NULL, func, NULL) != 0)
        err(1, "pthread_create()");

    pthread_mutex_lock(&mutex);

    while (!done)
        pthread_cond_wait(&cond, &mutex);

    pthread_mutex_unlock(&mutex);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    pthread_join(thread, NULL);

    return 0;
}

如果我在 pthread_cond_signal 之后调用 pthread_mutex_unlock,报告就会消失。但是手册说 pthread_cond_signal 不需要获取互斥锁。是否在任何地方记录了调用 pthread_cond_signal 作为引用 pthread_cond_t 的最后一次调用,并从执行 pthread_cond_wait 的线程中销毁它是不合法的?

最后调用pthread_cond_signal是完全合法的。 而且也不需要为 pthread_cond_signal.
保留互斥量 但在您的情况下,如果您不这样做,则可能会造成数据竞争。这是非法的。
在您的情况下,当您的线程解锁互斥锁时,您的主线程可能会开始 运行 并破坏条件。这是您的数据竞赛。

所以你有两种可能。
要么你在调用 pthread_cond_signal 时持有互斥锁,这样你的主线程在使用它之前无法访问条件。
或者你可以先加入你的线程,然后破坏条件。两者都可以。

但总的来说,我建议坚持 std::threadstd::mutexstd::condition_variable

Is it documented anywhere that calling pthread_cond_signal as the last call that references a pthread_cond_t, and destroying it from the thread that does pthread_cond_wait is not legal ?

它们无法涵盖文档中的所有极端情况。是的,在互斥量解锁后调用 pthread_cond_signal() 是合法的,在可以调用 pthread_cond_signal() 的同时销毁它是不合法的。

在加入可能仍在使用它的线程之前销毁条件变量和互斥锁是个坏主意。考虑这种情况:

  1. Main 创建线程并被中断。
  2. func() 一直运行到 pthread_mutex_unlock,但在发出信号之前被中断。
  3. main继续,done为真,所以从不等待,解锁互斥量并销毁条件变量,然后等待线程退出。
  4. func() 在被破坏的条件变量上调用 pthread_cond_signal - 可能是个坏主意。

修复:

将连接移动到两个 "destroys" 之前。

在您的示例中,我认为将互斥量保持更长的时间可以防止这种情况发生,但我认为这对于大型软件来说不是一种可持续的方法。

我不建议在完全明显之前销毁任何同步对象(互斥体、条件等),所有应该使用它们的线程都已加入或由其他显式同步保证永远不会访问再次同步对象。