C-如何制作线程等待Linux中的另一个



例如,我想创建5个线程并打印它们。如何使第四个执行在第二个之前执行?我尝试用静音锁定它,但我不知道如何仅使第二个锁定,所以它给了我分割错误。

通常,您定义了操作顺序,而不是执行这些操作的线程。这听起来像是一个微不足道的区别,但是当您开始实施它时,您会发现它带来了很大的不同。这也是更有效的方法,因为您不考虑所需的线程数量,而是要完成的操作或任务的数量,以及可以并行完成多少个线程,以及它们可能需要如何成为订购或测序。

出于学习目的,看订购线程可能是有意义的。

OP将指针传递给每个工作线程功能的字符串。有效,但有些奇怪。通常,您将通过整数标识符:

#include <stdlib.h>
#include <inttypes.h>
#include <pthread.h>
#define  ID_TO_POINTER(id)  ((void *)((intptr_t)(id)))
#define  POINTER_TO_ID(ptr) ((intptr_t)(ptr))

ID类型的转换 - 我认为是上面的签名整数,通常是intlong-通过两个铸件完成指针。第一个铸件是在<stdint.h>中定义的intptr_t类型(当您包括<inttypes.h>时将自动包含在内(,这是一种符号整数类型,可以保留任何void指针的值;第二个演员是无效的指针。中级演员会避免警告,以防您的ID是一个整数类型,该整数类型无法转换为无效指针,而不会潜在的信息丢失(通常在警告中描述为"不同大小"(。

订购posix线程的最简单方法,与订购操作,任务或作业不同,是使用单个静音词作为保护接下来应运行的线程的ID和相关条件变量的锁要等待线程,直到出现ID。

剩下的一个问题是如何定义顺序。通常,您只是简单地增加或减小ID值 - 降低意味着线程将以ID值的降序运行,但是ID值为-1(假设您从0到0到达的线程数字(总是意味着"所有已完成的所有完成",无论使用的线程数量如何:

static pthread_mutex_t  worker_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   worker_wait = PTHREAD_COND_INITIALIZER;
static int              worker_id   = /* number of threads - 1 */;
void *worker(void *dataptr)
{
    const int id = POINTER_TO_ID(dataptr);
    pthread_mutex_lock(&worker_lock);
    while (worker_id >= 0) {
        if (worker_id == id) {
            /* Do the work! */
            printf("Worker %d running.n", id);
            fflush(stdout);
            /* Choose next worker */
            worker_id--;
            pthread_cond_broadcast(&worker_wait);
        }
        /* Wait for someone else to broadcast on the condition. */
        pthread_cond_wait(&worker_wait, &worker_lock);
    }
    /* All done; worker_id became negative.
       We still hold the mutex; release it. */
    pthread_mutex_unlock(&worker_lock);
    return NULL;
}

请注意,我没有让工人完成任务后立即退出;这是因为我想稍微扩展示例:假设您想在数组中定义操作顺序:

static pthread_mutex_t  worker_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   worker_wait = PTHREAD_COND_INITIALIZER;
static int              worker_order[] = { 0, 1, 2, 3, 4, 2, 3, 1, 4, -1 };
static int             *worker_idptr = worker_order;
void *worker(void *dataptr)
{
    const int id = POINTER_TO_ID(dataptr);
    pthread_mutex_lock(&worker_lock);
    while (*worker_idptr >= 0) {
        if (*worker_idptr == id) {
            /* Do the work! */
            printf("Worker %d running.n", id);
            fflush(stdout);
            /* Choose next worker */
            worker_idptr++;
            pthread_cond_broadcast(&worker_wait);
        }
        /* Wait for someone else to broadcast on the condition. */
        pthread_cond_wait(&worker_wait, &worker_lock);
    }
    /* All done; worker_id became negative.
       We still hold the mutex; release it. */
    pthread_mutex_unlock(&worker_lock);
    return NULL;
}

看到几乎没有变化?

让我们考虑第三种情况:一个单独的线程,例如主线程,决定下一个线程将运行。在这种情况下,我们需要两个条件变量:一个供工人等待,另一个要等待。

static pthread_mutex_t  worker_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t   worker_wait = PTHREAD_COND_INITIALIZER;
static pthread_cond_t   worker_done = PTHREAD_COND_INITIALIZER;
static int              worker_id = 0;
void *worker(void *dataptr)
{
    const int id = POINTER_TO_ID(dataptr);
    pthread_mutex_lock(&worker_lock);
    while (worker_id >= 0) {
        if (worker_id == id) {
            /* Do the work! */
            printf("Worker %d running.n", id);
            fflush(stdout);
            /* Notify we are done. Since there is only
               one thread waiting on the _done condition,
               we can use _signal instead of _broadcast. */
            pthread_cond_signal(&worker_done);
        }
        /* Wait for a change in the worker_id. */
        pthread_cond_wait(&worker_wait, &worker_lock);
    }
    /* All done; worker_id became negative.
       We still hold the mutex; release it. */
    pthread_mutex_unlock(&worker_lock);
    return NULL;
}

确定工人应首先运行的线程应在创建工作线程时持有worker_lock Mutex,然后在worker_done条件变量上等待。当第一个工人完成任务时,它将在worker_cone条件变量上发出信号,并在worker_wait条件变量上等待。然后,决定器线程应将worker_id更改为应运行的下一个ID,并在worker_wait条件变量上广播。这会继续,直到决定器线程将worker_id设置为负值。例如:

int             threads; /* number of threads to create */
pthread_t      *ptids;   /* already allocated for that many */    
pthread_attr_t  attrs;
int             i, result;
/* Simple POSIX threads will work with 65536 bytes of stack
   on all architectures -- actually, even half that. */
pthread_attr_init(&attrs);
pthread_attr_setstacksize(&attrs, 65536);
/* Hold the worker_lock. */
pthread_mutex_lock(&worker_lock);
/* Create 'threads' threads. */
for (i = 0; i < threads; i++) {
    result = pthread_create(&(ptids[i]), &attrs, worker, ID_TO_POINTER(i));
    if (result) {
        fprintf(stderr, "Cannot create worker threads: %s.n", strerror(result));
        exit(EXIT_FAILURE);
    }
}
/* Thread attributes are no longer needed. */
pthread_attr_destroy(&attrs);
while (1) {
    /* 
       TODO: Set worker_id to a new value, or
             break when done.
    */
    /* Wake that worker */
    pthread_cond_broadcast(&worker_wait);
    /* Wait for that worker to complete */
    pthread_cond_wait(&worker_done, &worker_lock);
}
/* Tell workers to exit */
worker_id = -1;
pthread_cond_broadcast(&worker_wait);
/* and reap the workers */
for (i = 0; i < threads; i++)
    pthread_join(ptids[i], NULL);

在上述所有示例中都有一个非常重要的细节,如果没有很多练习,可能很难理解:静音和条件变量相互作用的方式(如果通过pthread_cond_wait()配对(。

当线程调用pthread_cond_wait()时,它将原子上释放指定的静音,并在条件变量上等待新的信号/广播。"原子"意味着两者之间没有时间;两者之间什么都不会发生。收到信号或广播时的呼叫返回 - 区别在于,信号仅为一个随机服务员。广播到达条件变量上的所有线程 - ,该线程获取锁。您可以将其视为信号/广播首先醒来的线程,但是pthread_cond_wait()只有在重新呼吸时才会返回。

这种行为在上面的所有示例中都隐含地使用。特别是,您会注意到pthread_cond_signal()/pthread_cond_broadcast()始终在持有worker_lock MUTEX时完成;这样可以确保其他线程或线程仅在worker_lock MUTEX被解锁后才醒来并进行起作用 - 要么明确或通过等待条件变量的保持线程。

我以为我可能会绘制一个有向图的图表(使用GraphViz(,以了解事件和动作的顺序,但是此"答案"已经太长了。我建议您自己做 - 也许在纸上? - 因为当我学习所有这些东西时,这种可视化对我自己非常有用。

&nbsp;
我必须承认,我对上述计划感到非常不舒服。任何一次,只有一个线程正在运行,这基本上是错误的:任何应以特定顺序完成任务的作业,只需需要一个线程。

但是,我展示了上述示例为您为您提供(不仅是OP,而且对Posix线程感兴趣的任何C程序员(,以使如何使用静音和条件变量更加自在。

最新更新