C 中的 pthread:我不明白为什么 pthread_cond_signal 没有唤醒一个线程

pthread in C : i can't figure why pthread_cond_signal is not waking up one thread

我想要一个由多个线程完成的例程,一旦它们被创建,它们需要在复制它们计算的东西之前完成它们的工作。因此,一旦完成工作,线程就会打开 cond_wait。

现在可以运行的一段代码:

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

#define MAX_RAYCAST_THREADS 2

typedef struct      s_thread_env
{
    int             id;
    pthread_t       thread;
    int             work_done;
    void            *shared_data;
}                   t_thread_env;

typedef struct      s_shared_data
{
    t_thread_env    *tab_thread_env;
    int             max_thread;
    int             all_work_done;
    pthread_mutex_t mutex;
    pthread_cond_t  cond;
    int             stop;
}                   t_shared_data;

void    set_threads_again(int id, t_shared_data *shared_data)
{
    int i;

    shared_data->all_work_done = 0;
    i = -1;
    while (++i < shared_data->max_thread)
        shared_data->tab_thread_env[i].work_done = 0;
    //i = -1;
    //while (++i < shared_data->max_thread)
    //{
        //if (i != id)
        //{
            //printf("cond_signal to thread %i\n", i);
            //pthread_cond_signal(&shared_data->cond);
        //}
    //}
    pthread_cond_broadcast(&shared_data->cond);
}

void    wait_or_signal(t_thread_env *thread_env, t_shared_data *shared_data)
{
    int i;

    i = 0;
    while (i < shared_data->max_thread && shared_data->tab_thread_env[i].work_done)
        i++;
    if (i == shared_data->max_thread)
    {
        printf("    allworkdone sent by thread %i\n", thread_env->id);
        //printf("      copy_screenpixels() by thread %i\n", thread_env->id);

        set_threads_again(thread_env->id, shared_data);
    }
    else
    {
        printf("    thread number %i is waiting for other threads\n", thread_env->id);
        pthread_cond_wait(&shared_data->cond, &shared_data->mutex);
        printf("ENFIN ! thread number %i woke up from condwait\n", thread_env->id);
    }
}

void    *routine(void *arg)
{
    t_thread_env    *thread_env;
    t_shared_data   *shared_data;
    int             stop;

    thread_env = (t_thread_env *)arg;
    shared_data = (t_shared_data *)thread_env->shared_data;
    pthread_mutex_lock(&shared_data->mutex);
    while (!shared_data->stop)
    {
        printf("new frame> thread_id = %i, thread_env->work_done = %i\n", thread_env->id, thread_env->work_done);
        pthread_mutex_unlock(&shared_data->mutex);
        printf("        raycast() in routine thread %i\n", thread_env->id);
        pthread_mutex_lock(&shared_data->mutex);
        thread_env->work_done++;
        wait_or_signal(thread_env, shared_data);
    }
    pthread_mutex_unlock(&shared_data->mutex);
    return (0);
}

void    init_thread_env(t_shared_data *shared_data, int i)
{
    t_thread_env    *thread_env;

    thread_env = &shared_data->tab_thread_env[i];
    thread_env->id = i;
    thread_env->shared_data = shared_data;
    if (pthread_create(&thread_env->thread, NULL, routine, thread_env) != 0)
        printf("Error pthread_create for %i\n", i);
}

void    free_all(t_shared_data *shared_data)
{
    int i;

    pthread_mutex_lock(&shared_data->mutex);
    shared_data->stop = 1;
    pthread_mutex_unlock(&shared_data->mutex);
    i = -1;
    while (++i < shared_data->max_thread)
        pthread_join(shared_data->tab_thread_env[i].thread, NULL);
    printf("\nEND\n\n");
    //free etc
}

int     main()
{
    int             i;
    t_shared_data   *shared_data;

    shared_data = (t_shared_data*)malloc(sizeof(t_shared_data)); // if (!shared data){free etc}
    shared_data->max_thread = MAX_RAYCAST_THREADS;
    pthread_mutex_init(&shared_data->mutex, 0);
    pthread_cond_init(&shared_data->cond, 0);
    shared_data->tab_thread_env = (t_thread_env*)malloc(sizeof(t_thread_env) * shared_data->max_thread);
    i = -1;
    while (++i < shared_data->max_thread)
        init_thread_env(shared_data, i);
    while (1)
        sleep(1); //program is turning
    free_all(shared_data);
    return (0);
}

正确输出:

new frame> thread_id = 0, thread_env->work_done = 0
                raycast() in routine thread 0
        thread number 0 is waiting for other threads
new frame> thread_id = 1, thread_env->work_done = 0
                raycast() in routine thread 1
        allworkdone sent by thread 1
cond_signal to thread 0
new frame> thread_id = 1, thread_env->work_done = 0
ENFIN ! thread number 0 woke up from condwait
new frame> thread_id = 0, thread_env->work_done = 0
                raycast() in routine thread 0
        thread number 0 is waiting for other threads
                raycast() in routine thread 1
        allworkdone sent by thread 1
cond_signal to thread 0
new frame> thread_id = 1, thread_env->work_done = 0
ENFIN ! thread number 0 woke up from condwait
new frame> thread_id = 0, thread_env->work_done = 0
                raycast() in routine thread 0
        thread number 0 is waiting for other threads
                raycast() in routine thread 1

感谢您阅读我,祝您有美好的一天!

编辑:我制作了一个更具可读性和可编译性的版本,只有 1 个互斥体(旧版本:https://pastebin.com/4zMyBJi2)。

EDIT2:删除了原 post 的一些部分,并试图避免每次数据竞争,我的代码仍然有一些(因为它仍然不起作用)。但我想我快要开始工作了

EDIT3:好的,现在可以使用了,我编辑了代码。主要问题是我对 shared_data 变量的灾难性使用。

I tried to make my raycasting threads work using 1 call of pthread_create for each thread (in an initialisation function). Is it possible to do it?

每次成功调用 pthread_create 都会创建一个线程,因此这是 唯一的 方法。但是不要混淆线程的启动函数和线程本身。可以为 运行 同一个线程函数创建多个线程,但这需要多次调用 pthread_create,每个线程一个。

I guess it is better (for performances) to do it in this way (rather than an enormous amount of pthread_create and pthread_join calls), is this correct?

已经选择使用一定数量的线程来完成某些工作,pthread_create调用的数量已经确定。如果您有性能问题,那么他们应该关注使用多少线程、它们要执行的工作的详细信息以及它们同步的性质和粒度。

In order to make it happen, the last thread (number n) to finish his job has to tell the other ones that every thread has finished, so there is a pthread_cond_wait for the (n - 1) first threads, and the last thread (number n) calls pthread_cond_signal for each (n - 1) first ones. Each thread has his own mutex.

这似乎有点矫枉过正,即使您可以使它在技术上是正确的。每个线程都有一个互斥锁没有多大意义,因为 互斥锁只有在不同线程使用同一个 时才有用。但是,使用不同的互斥锁来保护不同的共享资源可能是有意义的。您可能需要其中一些的简历,但不是全部。

这似乎是(至少一个区域)您的代码失败的地方。您的线程未正确同步,因为它们都依赖 不同的 互斥体来保护 相同的 资源。我不清楚你的程序是否有不止一个互斥体的意义,但我会首先减少你的使用量。