具有多个生产者的高效消费者线程

时间:2010-08-26 06:52:15

标签: multithreading thread-safety producer-consumer

我试图通过跳过昂贵的事件操作来提高生产者/消费者线程的效率,如果有必要,可以使用以下内容:

//cas(variable, compare, set) is atomic compare and swap
//queue is already lock free

running = false


// dd item to queue – producer thread(s)

if(cas(running, false, true))
{
  // We effectively obtained a lock on signalling the event
  add_to_queue()
  signal_event()
}
else
{
  // Most of the time if things are busy we should not be signalling the event
  add_to_queue()

  if(cas(running, false, true))
    signal_event()
}

...

// Process queue, single consumer thread

reset_event()

while(1)
{
  wait_for_auto_reset_event() // Preferably IOCP

  for(int i = 0; i &lt SpinCount; ++i)
    process_queue()

  cas(running, true, false)

  if(queue_not_empty())
    if(cas(running, false, true))
      signal_event()
}

显然试图让这些事情变得正确有点棘手(!)上面的伪代码是否正确?一个解决方案可以向事件发出超过完全需要的信号,但不是每个项目都能这样做。

4 个答案:

答案 0 :(得分:2)

这属于“停止搞乱并重新开始工作”的子类别,称为“过早优化”。 : - )

如果“昂贵的”事件操作占用了相当长的一部分时间,那么您的设计是错误的,而不是使用生产者/消费者,您应该使用临界区/互斥体,并从调用线程中完成工作。

如果您真的担心,我建议您分析您的申请。

<强>更新

正确答案:

生产者

ProducerAddToQueue(pQueue,pItem){

    EnterCriticalSection(pQueue->pCritSec)
        if(IsQueueEmpty(pQueue)){
            SignalEvent(pQueue->hEvent)
        }

        AddToQueue(pQueue, pItem)
    LeaveCriticalSection(pQueue->pCritSec)
}

消费

nCheckQuitInterval = 100; // Every 100 ms consumer checks if it should quit.

ConsumerRun(pQueue)
{
    while(!ShouldQuit())
    {
        Item* pCurrentItem = NULL;
        EnterCriticalSection(pQueue-pCritSec);
            if(IsQueueEmpty(pQueue))
            {
                ResetEvent(pQueue->hEvent)
            }
            else
            {
                pCurrentItem = RemoveFromQueue(pQueue);
            }
        LeaveCriticalSection(pQueue->pCritSec);

        if(pCurrentItem){
            ProcessItem(pCurrentItem);
            pCurrentItem = NULL;
        }
        else
        {
            // Wait for items to be added.
            WaitForSingleObject(pQueue->hEvent, nCheckQuitInterval);
        }

    }
}

注意:

  • 该事件是手动重置事件。
  • 受关键部分保护的操作很快。仅当队列转换为空状态或从空状态转换时才设置或重置事件。必须在关键部分设置/重置它以避免竞争条件。
  • 这意味着关键部分只能保留很短的时间。所以争论很少见。
  • 除非争用,否则关键部分不会阻止。所以上下文切换很少见。

假设:

  • 这是一个真正的问题而不是作业。
  • 生产者和消费者花费大部分时间做其他事情,即准备好队列中的物品,并在将它们从队列中移除后进行处理。
  • 如果他们大部分时间都在进行实际的队列操作,那么您不应该使用队列。我希望这很明显。

答案 1 :(得分:0)

经过一堆案件,看不出问题。但它有点复杂。我想也许你会遇到queue_not_empty / add_to_queue赛车的问题。但看起来两条路径中的后主导CAS都涵盖了这种情况。

CAS价格昂贵(不像信号那么贵)。如果您希望跳过信号是常见的,我会按如下方式编写CAS代码:

bool cas(variable, old_val, new_val) {
   if (variable != old_val) return false
   asm cmpxchg
}

像这样的无锁结构Jinx(我工作的产品)非常擅长测试。因此,您可能希望使用eval许可证来测试无锁队列和信号优化逻辑。


编辑:也许你可以简化这种逻辑。

running = false 

// add item to queue – producer thread(s) 
add_to_queue()
if (cas(running, false, true)) {
   signal_event()
}

// Process queue, single consumer thread 

reset_event() 

while(1) 
{ 
    wait_for_auto_reset_event() // Preferably IOCP 

   for(int i = 0; i &lt SpinCount; ++i) 
       process_queue() 

   cas(running, true, false)  // this could just be a memory barriered store of false

   if(queue_not_empty()) 
      if(cas(running, false, true)) 
         signal_event() 
} 

现在cas / signal总是彼此相邻,它们可以移动到子程序中。

答案 2 :(得分:0)

为什么不将bool与活动相关联?使用cas将其设置为true,如果cas成功,则发出事件信号,因为事件必须已清除。然后服务员可以在等待之前清除标志

bool flag=false;

// producer
add_to_queue();
if(cas(flag,false,true))
{
    signal_event();
}

// consumer
while(true)
{
    while(queue_not_empty())
    {
        process_queue();
    }
    cas(flag,true,false); // clear the flag
    if(queue_is_empty())
        wait_for_auto_reset_event();
}

这样,您只需等待队列中没有元素,并且您只为每批项目发出一次事件信号。

答案 3 :(得分:0)

我相信,你想要在这个问题上实现这样的目标:
WinForms Multithreading: Execute a GUI update only if the previous one has finished.它特定于C#和Winforms,但结构可能适用于您。