C语言中的多写入线程安全队列

C语言中的多写入线程安全队列,c,thread-safety,queue,pthreads,C,Thread Safety,Queue,Pthreads,我正在使用pthreads开发一个多线程C应用程序。我有一个线程向数据库写入数据(数据库库只能在单个线程中使用),还有几个线程正在收集数据、处理数据,然后需要将结果发送到数据库线程进行存储。我在前面提到的中看到,在C中创建一个多编写器安全队列是“可能的”,但我看到的每个地方都简单地说它“对于这个示例来说太复杂了”,并且只演示了一个编写器安全队列 我需要以下东西: 高效的插入和移除。我假设像任何其他队列一样,O(1)排队和退队是可能的。 动态分配的内存,即链接结构。我不需要对队列的大小有任意的限

我正在使用pthreads开发一个多线程C应用程序。我有一个线程向数据库写入数据(数据库库只能在单个线程中使用),还有几个线程正在收集数据、处理数据,然后需要将结果发送到数据库线程进行存储。我在前面提到的中看到,在C中创建一个多编写器安全队列是“可能的”,但我看到的每个地方都简单地说它“对于这个示例来说太复杂了”,并且只演示了一个编写器安全队列

我需要以下东西:

  • 高效的插入和移除。我假设像任何其他队列一样,O(1)排队和退队是可能的。
  • 动态分配的内存,即链接结构。我不需要对队列的大小有任意的限制,所以数组确实不是我想要的。

编辑:读取线程不应该在一个空队列上旋转,因为可能有几分钟的时间没有写入,并且有短时间的大量写入

我会选择多个单编写器队列(每个编写器线程一个)。然后,您可以检查如何让单个读卡器读取各种队列。

如果您不需要无锁队列,则可以使用锁将现有队列包装起来

Mutex myQueueLock;
Queue myQueue; 
void mtQueuePush(int value)
{
    lock(myQueueLock);
    queuePush(myQueue, value);
    unlock(myQueueLock);
}
int mtQueueNext()
{
    lock(myQueueLock);
    int value = queueFront(myQueue);
    queuePop(myQueue);
    unlock(myQueueLock);
    return value;
}
之后唯一的事情是在队列为空时为mtQueueNext添加某种处理

编辑: 如果您有一个单读写器无锁队列,您只需要在mtQueuePush周围有一个锁,以防止多个同时写入器


< >有很多单独的读写器队列,但是大多数都是以C++模板类来实现的。不过,如果需要的话,你可以通过谷歌搜索,找出如何用纯C语言重写它们。

当然,这里有无锁队列。不过,根据您在评论中所说的,这里的性能一点也不重要,因为您无论如何都要为每次写入创建一个线程

因此,这是一个条件变量的标准用例。使自己成为一个包含互斥体、条件变量、链接列表(或循环缓冲区,如果愿意)和取消标志的结构:

write:
    lock the mutex
    (optionally - check the cancel flag to prevent leaks of stuff on the list)
    add the event to the list
    signal the condition variable
    unlock the mutex

read:
   lock the mutex
   while (list is empty AND cancel is false):
       wait on the condition variable with the mutex
   if cancel is false:  // or "if list non-empty", depending on cancel semantics
       remove an event from the list
   unlock the mutex
   return event if we have one, else NULL meaning "cancelled"

cancel:
   lock the mutex
   set the cancel flag
   (optionally - dispose of anything on the list, since the reader will quit)
   signal the condition variable
   unlock the mutex
如果您使用的是带有外部节点的列表,那么您可能希望在互斥锁之外分配内存,以减少其保留的时间。但是,如果使用侵入式列表节点设计事件,这可能是最简单的

编辑:如果在“取消”中将“信号”更改为“广播”,则还可以支持多个读卡器(不保证其中一个读卡器获得给定事件)。虽然你不需要它,但它也不需要任何东西。

用C语言编写的无锁数据结构库


具有M&S队列。

当您说“多个写入程序”时,您的意思是希望队列支持来自多个线程的push()和pop()?您是否正在寻找无锁/无锁实现?您的意思是两个或多个写入程序线程同时添加到队列中的队列,或者一个队列有多个可能的写入线程,但其中只有一个同时写入队列?-多个写入线程意味着多个push()-ing线程。-无锁是不需要的任何方式,但会很好。-并发写入是完全可能的,尽管可能性不大。(即,没有隐含的保证,不会有并发写入,但如果一个或多个块,直到一个完成,这不是一个大问题。)BTW,一个原因,人们可能没有发布的实现是这种代码是令人厌倦的,虽然不太难写在C.,这是更简单的C++。如果你不完全喜欢C,我建议你改变。不幸的是,这不太可能,因为许多线程正在生成,完成它们的任务,然后将结果写入一个队列,由一个数据库工作线程处理。因此,每个队列只会被写入一次,从而破坏了整个目的。每个线程都有如此重要的工作,在最后只对队列进行一次写入?因此,队列实际上不是一个性能问题。用互斥锁简单地保护push方法怎么样?这是一种可能性,尽管我希望在队列中有多个项目的情况下,读卡器可以在前面弹出(),而写卡器push()在后面弹出()。在这种情况下,不应该有互斥的必要。也就是说,虽然我不反对在设计中使用互斥,但我还是不想把所有该死的东西都封装在一个里面。这行不通。当有读卡器等待队列中的项目时,它会持有锁,从而防止任何写入程序向队列中添加项目(假设queueFront()/queuePop()正在阻塞)。如果queueFront()/queuePop()未阻塞,则读取器无法在不进行轮询的情况下等待项目。我可以看到一个问题:当我决定关闭应用程序时,需要停止正在等待条件变量的读取器线程。我该怎么做呢?内存可以被任意数量的执行线程同时读取。因此,读取操作不需要互斥锁。@朱利安:虽然在本文中我们称该操作为“读取”,但它会从队列中删除一项。这绝对需要锁来完成,当然它也需要锁来等待条件变量。回答不错,这个库看起来很有趣,尽管它是匿名的,但在网上没有太多。尽管如此,它的源代码数量还是可以管理的,所以应该可以接受代码审查:)