多生产者单一消费者懒惰任务执行

时间:2013-02-28 22:55:24

标签: multithreading

我正在尝试建模一个系统,其中有多个线程产生数据,一个线程消耗数据。诀窍是我不希望专用线程使用数据,因为所有线程都存在于池中。相反,我希望其中一个生产者在有工作时清空队列,如果另一个生产者已经清空队列则产生。

基本思想是有一个工作队列,并锁定处理。每个生产者将其有效负载推送到队列,然后尝试进入锁定。该尝试是非阻塞的,并返回true(获取锁定)或false(锁定由其他人持有)。

如果获取了锁,则该线程处理队列中的所有数据,直到它为空(包括其他生成器在处理期间引入的任何新有效负载)。处理完所有工作后,线程将释放锁并退出。

以下是该算法的C ++代码:

void Process(ITask *task) {
     // queue is a thread safe implementation of a regular queue
     queue.push(task);

     // crit_sec is some handle to a critical section like object
     // try_scoped_lock uses RAII to attempt to acquire the lock in the constructor
     //                 if the lock was acquired, it will release the lock in the
     //                 destructor
     try_scoped_lock lock(crit_sec);

     // See if this thread won the lottery. Prize is doing all of the dishes
     if (!lock.Acquired())
        return;

     // This thread got the lock, so it needs to do the work
     ITask *currTask;
     while (queue.try_pop(currTask)) {
          ... execute task ...
     }
}

一般来说,这段代码工作正常,我从未亲眼目睹过我将在下面描述的行为,但这种实现让我感到不安。理所当然的是,在线程退出while循环和释放临界区之间引入了竞争条件。

整个算法依赖于这样的假设,即如果保持锁定,则线程正在为队列提供服务。

我基本上是在寻找两个问题的启示:

  1. 我是否更正了所描述的种族情况(其他种族的奖金)
  2. 是否有实施此机制的标准模式,并且不会引入竞争条件?

1 个答案:

答案 0 :(得分:2)

是的,有竞争条件。

线程A添加任务,获取lock,自行处理,然后从queue请求任务。它被拒绝了。

此时线程B向queue添加任务。然后它尝试获取锁定并失败,因为线程A具有锁定。线程B退出。

然后

线程A退出,queue非空,并且没有人处理任务。

这很难找到,因为那个窗口相对较窄。为了使它更容易找到,在while循环后引入“睡眠10秒”。在调用代码中,插入任务,等待5秒,然后插入第二个任务。再过10秒钟,检查两个插入任务是否都已完成,还有一个任务要在queue处理。

解决此问题的一种方法是将try_pop更改为try_pop_or_unlock,然后将lock传递给它。 try_pop_or_unlock然后以原子方式检查空queue,如果是,则解锁lock并返回false。

另一种方法是改进线程池。添加一个基于计数信号量的“消耗”任务启动器。

semaphore_bool bTaskActive;
counting_semaphore counter;

when (counter || !bTaskActive)
  if (bTaskActive)
    return
  bTaskActive = true
  --counter
  launch_task( process_one_off_queue, when_done( [&]{ bTaskActive=false ) );

当计数信号量处于活动状态时,或者当完成的消耗任务调用时,如果没有消耗任务处于活动状态,它将启动一个消耗任务。

但这只是我的头脑。