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

Multithreading 具有多个生产者的高效消费线程,multithreading,thread-safety,producer-consumer,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

我正在尝试通过跳过昂贵的事件操作(如有必要)来提高生产者/消费者线程的效率,例如:

//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()
}

显然,试图纠正这些事情有点棘手(!),那么上面的伪代码正确吗?如果一个解决方案能够向事件发出比实际需要更多的信号,那么这个解决方案是可以的,但不是每个项目都能发出信号。

通过了一系列案例,没有发现问题。但这有点复杂。我想你可能会对队列不为空/添加队列竞赛有问题。但这两条道路上的后主导CAS似乎都涵盖了这一情况

CAS价格昂贵(没有信号贵)。如果您认为跳过信号很常见,我将对CAS进行如下编码:

bool cas(variable, old_val, new_val) {
   if (variable != old_val) return false
   asm cmpxchg
}
像这样的无锁结构是(我工作的产品)非常擅长测试的东西。因此,您可能希望使用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/信号总是彼此相邻,它们就可以移动到一个子例程中。

为什么不将
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();
}

通过这种方式,您只在队列中没有元素的情况下等待,并且只为每批项目发送一次事件信号。

我相信,您希望实现以下问题:

它在C#和Winforms上是特定的,但是这种结构可能非常适合您。

这属于“停止胡闹,回去工作”的子类别,称为“过早优化”。-)

如果“昂贵的”事件操作占用了大量时间,那么您的设计是错误的,您应该使用关键部分/互斥,而不是使用生产者/消费者,只从调用线程执行工作

如果你真的很担心的话,我建议你对你的申请进行分析

更新:

正确答案:

制作人

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);
        }

    }
}
注:

  • 该事件为手动重置事件
  • 受关键部分保护的操作速度很快。仅当队列转换为空状态或从空状态转换为空状态时,才会设置或重置事件。必须在临界段内进行设置/重置,以避免出现争用情况
  • 这意味着临界截面仅保持很短的时间。因此,争论将是罕见的
  • 关键部分不会阻塞,除非它们被争用。因此,上下文切换将很少见
假设:

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

您的代码是用哪种语言编写的?事件操作是否仅昂贵,还是使用者线程处理也昂贵?(你也想避免吗?)对不起,我现在才注意到所有这些反馈!它是C++的,虽然我对在CpHART中复制类似的东西感兴趣。如果对队列中的每个项目都执行事件同步操作,那么它是最昂贵的。很抱歉,我错过了所有这些评论,直到现在-感谢您的评论我只是想花点时间来消化一下你现在所说的……在你评论的第一部分(我开始记得我以前的思路!)-是的,我是故意(我想!)用这种方式来处理我记得在安东尼的回答这样一个例子中造成的种族状况。我想我是在追求较小的邪恶——在某些情况下,让工作人员循环的次数可能比最坏情况下需要的次数要多。我现在正在更多地消化你的建议和你对内存屏障存储的评论。我以前一直在想类似的事情,但决定尝试让它与互锁的东西一起工作第一:)对不起,忽略我的最后一条评论,我点击返回太快了!-我想我试过类似的方法,但发现在清除标志位和队列为空之间存在竞争条件。很抱歉,这是一个非常糟糕的答案,尽管我感谢您的帮助。关键部分和互斥体对于我正在使用的吞吐量来说太慢了。其中很多都来自可能发生的操作系统上下文切换。@wb,您只是在重新设计关键部分。这正是关键部分要解决的问题,所以只需使用它们。除非有冲突,否则它们不会阻塞。要清楚,您建议的解决方案是使用内存屏障/联锁比较和交换操作,以避免必须发出信号或等待事件对象/信号量。这正是关键部分所做的,而你的例子正是他们这样做的原因。这就是为什么你的问题的答案是“使用关键部分”——不是因为你的想法有什么问题,而是因为它已经被发明出来了。