【问题标题】:Multiple-writer thread-safe queue in CC中的多写线程安全队列
【发布时间】:2010-11-15 19:19:45
【问题描述】:

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

我需要以下东西:

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

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

【问题讨论】:

  • 当你说“multiple-writer”时,你的意思是你希望队列支持来自多个线程的 push() 和 pop()?
  • 您在寻找无锁/无锁实现吗?
  • 您是指一个队列,其中两个或多个写入线程同时添加到队列中,还是一个队列有多个可能的写入线程,但一次只有一个写入队列?跨度>
  • - 多个编写器意味着多个 push()-ing 线程。 - 无论如何都不需要无锁,但会很好。 - 并发写入是完全可能的,尽管可能性不大。 (即没有隐含的保证不会有并发写入,但如果一个或多个块直到一个完成,这不是一个大问题。)
  • 顺便说一句,人们可能没有发布实现的一个原因是这种代码很烦人,但用 C 编写并不难。在 C++ 中要简单得多。如果你不完全依赖 C,我建议改变。

标签: c thread-safety queue pthreads


【解决方案1】:

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

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

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

如果您使用带有外部节点的列表,那么您可能希望在互斥锁之外分配内存,以减少其持有时间。但是,如果您使用侵入式列表节点设计事件,那可能是最简单的。

编辑:如果在取消时将“信号”更改为“广播”,您还可以支持多个阅读器(没有可移植的保证)。虽然你不需要它,但它也并不真正花费任何东西。

【讨论】:

  • 我可以看到一个问题:当我决定关闭应用程序时,我需要停止正在等待条件变量的读取器线程。我该怎么做呢?
  • 内存可以被任意数量的执行线程同时读取。因此,读取操作不需要互斥体。
  • @Julian:虽然在这种情况下我们将操作称为“读取”,但它会从队列中删除一个项目。它绝对需要锁才能做到这一点,当然它也需要锁才能等待条件变量。
【解决方案2】:

如果你不需要一个无锁队列,那么你可以只用一个锁包裹一个现有的队列。

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 重写它们。

【讨论】:

  • 这是一种可能性,尽管我希望在队列中有多个项目的情况下,读者可以 pop() 在前面,而编写者 push()-es 在背部。在这种情况下,应该不需要互斥。
  • 也就是说,虽然我不反对在设计中使用互斥锁,但我宁愿不将整个该死的东西封装在一个中。
  • 这行不通。当有读者在等待队列中的项目时,它会持有锁,从而防止任何编写者将项目添加到队列中(假设 queueFront()/queuePop() 正在阻塞)。如果 queueFront()/queuePop() 没有阻塞,那么阅读器就没有办法在没有轮询的情况下等待一个项目。
【解决方案3】:

http://www.liblfds.org

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

有 M&S 队列。

【讨论】:

  • 很好的答案,图书馆看起来很有趣,虽然相当匿名,网上没有太多关于它的内容。它仍然是可管理数量的源代码,因此应该可以进行代码审查:)
【解决方案4】:

我会选择多个单写队列(每个写线程一个)。然后你可以查看this,了解如何让单个阅读器阅读各种队列。

【讨论】:

  • 不幸的是,这不太可能,因为许多线程正在生成,完成它们的任务,然后将结果写入队列,由单个数据库工作线程处理。因此,每个队列只会被写入一次,从而破坏了整个目的。
  • 好的。每个线程的工作量如此之大,最后只对队列进行一次写入?因此,队列并不是真正的性能问题。用互斥锁简单地保护推送方法怎么样?
猜你喜欢
  • 1970-01-01
  • 2014-03-13
  • 2023-03-04
  • 2013-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-16
相关资源
最近更新 更多