两个pthreads之间的通信

时间:2015-08-20 09:54:59

标签: c multithreading queue pthreads fifo

在C程序中,某些线程(pthread1pthread2,...)生成一条消息,生成的消息由另一个线程(pthreadprint)处理,该线程打印它们。

消息可以在处理之前“积聚”:根据pthreadprint打印所需的时间,多个消息可以在特定时间作为pthreadprint的输入进行堆叠。我显然不知道在最糟糕的情况下可以等待处理的最大消息数。

将这些消息(从pthread1pthread2,......“生成”它们发送到打印出来的pthreadprint的最佳方式是什么?它们不仅应该传播,还应该“储存”。我知道互斥锁和条件变量,但它们不代表队列:是否可以使用所有线程都可以访问的FIFO队列?

2 个答案:

答案 0 :(得分:1)

针对这类生产者 - 消费者问题的一个简单而强大的解决方案是使用由互斥锁保护的单个链接的消息列表。使用C99和pthreads:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>

struct message {
    struct message *next;
    /* Payload is irrelevant, here just as an example: */
    size_t          size;
    char            data[];
};

typedef struct {
    pthread_mutex_t lock;
    pthread_cond_t  more;
    struct message *newest;
    struct message *oldest;
} queue;
#define QUEUE_INITIALIZER { PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, NULL, NULL }

/* Nonblocking variant */
struct message *try_dequeue(queue *const q);

/* Blocking variants */
int enqueue(queue *const q, struct message *const m);
struct message *dequeue(queue *const q);

/* Suggested interface for queueing a new message */
int queue_printf(queue *const q, const char *const format, ...);

实施很简单。

  • struct message单链表首先包含最旧的邮件,并在末尾添加新邮件。
  • 空队列同时包含newest == NULLoldest == NULL
  • 在检查指针之前,所有队列操作(enqueue()dequeue()try_dequeue())都会使用队列lock互斥锁。 (为了减少大量使用时的争用,请尽量缩短锁定持续时间;换句话说,在锁定之前先完全构造消息。)
  • 阻塞出队呼叫可以等待more条件变量(当队列为空时)等待新消息。
  • 当第一条消息入队时,newestoldest都指向它。
  • 当排队第一条消息时,会发出条件变量more,如果有阻塞队列等待新消息的话。
  • 将更多消息设置为newest->next以先指向新消息,然后将newest设置为指向新消息。
  • oldest成员从列表中分离,将oldest更新为指向oldest->next。如果oldest变为NULL(然后newestoldest都指向同一条消息,队列中唯一的消息),newest也设置为{ {1}}因为队列变空了。
  • 如果锁定NULL互斥锁失败(并且通常只有在C库检测到死锁情况时才会失败),或者如果您有检查通知队列结构处于不一致状态,则排队消息才会失败(例如,locknewest中的一个,但不是两个,都是oldest。 上述原型中的逻辑是,如果成功则返回NULL,否则返回错误代码(0EINVAL)。我也想将EDEADLK设置为该错误代码,以便在出列时具有对称性。
  • 出列消息的原因与排队相同,加上队列为空(errno / EWOULDBLOCK)时也会失败。在这些情况下,该函数可以返回EAGAIN并设置NULL

如您所见,入队和出队都是O(1)操作,即占用恒定时间;无需在任何时候遍历整个列表。

enqueue / dequeue操作可以同时对多个消息进行/出列,只需重复上述操作即可。但是,在实践中很少需要这样做。 (对于出列,主要原因是如果你一次抓取多条消息,并且你有一条消息出现故障或错误,你必须处理错误和尚未处理但出列的消息; bug-容易。一个接一个地做事情更容易。此外,如果消息顺序并不重要,如果他们逐个出列消息,你总是可以让多个消费者并行工作。)

高级笔记:

如果依赖于C99标准,则可以对以errno开头的任何结构类型使用相同的代码。根据C99规则,这种结构是兼容的(对于那个共享的初始部分),这是队列操作访问的唯一部分。

换句话说,如果您有多个消息类型,每个消息都存储在自己的队列中,那么对于所有不同的消息类型,您只需要struct message *next; / enqueue() / dequeue()的一个实现,只要消息结构都以try_dequeue()开头。

对于类型安全,您需要简单的包装函数:

struct message *next;

当在头文件中定义时,实际上不应该生成任何开销 - 实际上,不应该生成任何其他代码,除非你出于某种原因取一个地址(其中) case一个非内联版本必须在每个编译单元中发出一个地址。但是,它们在编译时提供类型检查,这通常很有用。

答案 1 :(得分:0)

这似乎是进程间通信中“生产者 - 消费者”问题的典型第一年任务(可能是在OS类中?)

您需要的是一种将信息从流程传递到另一个流程的方法。有一些方法可以做到这一点,更简单的是“公共内存”,即所有进程都可以访问的一块内存。

在我看来,我建议实现一个队列。然后,您的所有进程都应该可以访问此队列,但只允许 ONLY (即来自互斥锁的结果)。您还可以通过添加检查大小的功能来扩充队列(例如int isQueueFull(void);,前提是每个队列节点也应该有一个索引,例如struct{ queueNode* nextNode; unsigned int index; void* data;})。

如果所有这些都在同一个初始进程中,然后从中启动线程,那么所有“子进程”都可以访问与父进程相同的内存空间。