多线程单读者单写入器fifo队列

时间:2009-09-19 10:15:41

标签: c++ windows linux multithreading message-queue

我需要一个队列来将消息从一个线程(A)传递到另一个线程(B),但是我没能找到一个真正做到我想要的东西,因为它们通常允许添加一个项目失败,一个案例在我的情况下非常致命,因为需要处理消息,并且线程真的无法停止并等待空余空间。

  • 只有线程A添加项目,只有线程B读取它们
  • 线程A必须永远不会阻塞,但是线程B不是性能关键,所以它可以
  • 添加项目必须始终成功,因此队列不能具有大小上限(缺少系统内存不足)
  • 如果队列为空,则线程B应该等到有待处理的项目

4 个答案:

答案 0 :(得分:7)

以下是如何在C ++中编写无锁队列:

http://www.ddj.com/hpc-high-performance-computing/210604448

但是当你说“线程A必须阻止”时,你确定这是要求吗? Windows不是实时操作系统(在正常使用中也不是linux)。如果您希望线程A能够使用所有可用的系统内存,那么它需要分配内存(或等待其他人执行)。如果读取器和写入器都采用进程内锁(即非共享互斥锁)来操作列表,则操作系统本身不能提供比您所拥有的更好的时序保证。添加消息的最坏情况是必须转到操作系统才能获得内存。

简而言之,您不喜欢的队列有一个固定容量的原因 - 这样他们就不必在所谓的低延迟线程中分配内存。

因此,无锁代码通常会减少阻塞,但由于内存分配不能保证,并且使用互斥锁的性能不应该是那么糟糕,除非你有一个真正庞大的流要处理的事件(例如,您正在编写网络驱动程序,而消息是传入的以太网数据包)。

所以,在伪代码中,我要尝试的第一件事就是:

Writer:
    allocate message and fill it in
    acquire lock
        append node to intrusive list
        signal condition variable
    release lock

Reader:
    for(;;)
        acquire lock
            for(;;)
                if there's a node
                    remove it
                    break
                else
                   wait on condition variable
                endif
            endfor
        release lock
        process message
        free message
    endfor

只有当这证明在编写器线程中引入了不可接受的延迟时,我才能使用无锁代码(除非我碰巧有一个合适的队列已经存在)。

答案 1 :(得分:1)

Visual Studio 2010正在添加2个支持此场景的新库,Asynchronous Agents Library和并行模式库。

代理库具有支持或异步消息传递,并包含用于向“目标”发送消息和从“源”接收消息的消息块

unbounded_buffer是一个模板类,它提供了我认为你正在寻找的东西:

#include <agents.h>
#include <ppl.h>
#include <iostream>

using namespace ::Concurrency;
using namespace ::std;

int main()
{
   //to hold our messages, the buffer is unbounded...
   unbounded_buffer<int> buf1;
   task_group tasks;

   //thread 1 sends messages to the unbounded_buffer
   //without blocking
   tasks.run([&buf1](){
      for(int i = 0 ; i < 10000; ++i)
         send(&buf1,i)
     //signal exit 
     send(&buf1,-1);
   });

   //thread 2 receives messages and blocks if there are none

   tasks.run([&buf1](){
      int result;
      while(result = receive(&buf1)!=-1)
      {
           cout << "I got a " << result << endl;
      }
   });

   //wait for the threads to end
   tasks.wait();
}

答案 2 :(得分:1)

  • 为什么不使用STL&lt; list&gt;或&lt; deque&gt;用互斥量添加/删除? thread-safety of STL不足吗?

  • 为什么不创建包含指针的自己的(单/双)链表节点类,并且要添加/删除的项继承自该指针?因此不需要额外的分配。您只需在threadA::add()threadB::remove()中点击几个指针就可以了。 (虽然你想在互斥体下做到这一点,但是除非你做了一些非常错误的事情,否则对threadA的阻塞效果可以忽略不计......)

  • 如果您使用的是pthread,请查看sem_post()sem_wait()。我们的想法是,threadB可以通过sem_wait()无限期地阻塞,直到threadA将某些东西放入队列。然后threadA调用sem_post()。这唤醒了threadB做它的工作。之后threadB可以回去睡觉。这是处理异步信令的有效方式,在threadA::add()完成之前支持多个threadB::remove()之类的内容。

答案 3 :(得分:0)

您可能想要考虑您的要求 - A是否真的无法丢弃任何队列项?或者你不希望B从队列中拉出两个连续的元素,这些元素不是连续的项目,因为这会以某种方式歪曲一系列事件?

例如,如果这是某种数据记录系统,你(可以理解)不会想要记录中的空白 - 但没有无限的记忆,现实是在某些角落的情况下你可能会超出你的队列容量..

在这种情况下,一种解决方案是拥有某种可以放入队列的特殊元素,这代表A发现它必须丢弃项目的情况。基本上你保留了一个额外的元素,大部分时间都是null。每次A向队列添加元素时,如果这个额外的元素不为空,那就进入。如果A发现队列中没有空间,那么它将这个额外的元素配置成'嘿,队列已满“

这样,A永远不会阻塞,你可以在系统非常繁忙时删除元素,但你不会忽略元素被删除的事实,因为只要队列空间可用,这个标记就会进入指出数据丢失的位置。当进程B发现它已将此超出标记元素从队列中拉出时,它会执行它需要做的任何事情。