使用QueueUserWorkItem(而不是.NET)等待工作项完成池化

时间:2009-08-18 09:54:48

标签: c++ winapi multithreading

我使用遗留的QueueUserWorkItem函数汇总了一些工作项(我需要在vista之前支持操作系统,所以

for( <loop thru some items >)
{
    QueueUserWorkItem( )
}

我需要等待这些才能继续下一步。 我见过几个类似的答案......但它们都在.NET中。 我正在考虑使用为每个项目存储一个事件并等待它们(喘气!),但是还有其他更好,轻量级的方法吗? (没有内核锁)

澄清:我知道使用事件。我对不需要内核级锁定的解决方案感兴趣。

7 个答案:

答案 0 :(得分:2)

AFAIK唯一能做到这一点的方法是在每项任务完成时都有一个InterlockIncrement计数器。

然后你可以做一个

while( counter < total )
    Sleep( 0 );

任务可以发出事件(或其他同步对象)的信号,您可以执行以下操作

while( count < total )
    WaitForSingleObject( hEvent, INFINITE );

第二种方法意味着主线程使用较少的处理时间。

编辑:TBH避免内核锁定的唯一方法是旋转锁定,这意味着你将有一个核心浪费时间,否则可以用来处理你的工作队列(或者其他任何东西)。如果你真的必须避免内核锁定,那么使用Sleep(0)旋转锁定。但是我绝对建议只使用内核锁,获得额外的CPU时间来进行有价值的处理并且不再担心“非”问题。

答案 1 :(得分:2)

如果您实际拆分执行堆栈,这可能没有任何等待:直到您在调用线程上执行的for循环,在完成最后一个队列线程的for循环之后:

 CallerFunction(...)
 {
   sharedCounter = <numberofitems>;
   for (<loop>)
   {
     QueueUserWorkItem(WorkerFunction, ...);
   }
   exit thread; (return, no wait logic)
 }

WorkerFunction(, ...) 
{
  // execute queued item here
  counter = InterlockeDdecrement(sharedCounter);
  if (0 == counter)
  {
    // this is the last thread, all others are done
   continue with the original logic from  CallerFunction here
  }
}

这是否有效取决于许多因素,我不能说不知道更多关于调用者上下文的信息,如果可以暂停其在调用线程上的执行并在排队的线程上恢复它。顺便说一句,“退出线程”我并不是指一个突然的线程中止,而是一个优雅的返回,并且整个调用堆栈准备将执行上下文移交给队列线程。我认为这不是一项微不足道的任务。

答案 2 :(得分:2)

以下是我过去成功使用过的方法:

将“完成”任务实施为参考计数对象。每个工作线程在执行其工作时都持有对该对象的引用,然后在完成时释放它。当ref计数达到0时,完成任务将起作用。

实施例

注意:经过多年主要使用C#后,我的C ++已经生锈了,因此请将以下示例视为伪代码

完成任务

class MyCompletionTask {

private:

    long _refCount;

public:

    MyCompletionTask() {
        _refCount = 0;
    }

public: // Reference counting implementation

   // Note ref-counting mechanism must be thread-safe,
   // so we use the Interlocked functions.

    void AddRef()
    {
        InterlockedIncrement(&_refCount);
    }

    void Release()
    {
        long newCount = InterlockedDecrement(&_refCount);

        if (newCount == 0) {
             DoCompletionTask();
             delete this;
        }
    }

private:

    void DoCompletionTask()
    {
        // TODO: Do your thing here
    }
}

致电代码

MyCompletionTask *task = new MyCompletionTask();

task->AddRef(); // add a reference for the main thread

for( <loop thru some items >)
{
    task->AddRef(); // Add a reference on behalf of the worker
                    // thread.  The worker thread is responsible
                    // for releasing when it is done.

    QueueUserWorkItem(ThreadProc, (PVOID)task, <etc> );
}

task->Release(); // release main thread reference

// Note: this thread can exit.  The completion task will run
// on the thread that does the last Release.

Thread Proc

void ThreadProc(void *context) {

    MyCompletionTask *task = (MyCompletionTask)context;

    //  TODO: Do your thing here

    task->Release();
}

使用此方法时要记住的一件事是完成任务完成的线程是非确定性的。它将取决于哪个工作线程首先完成(或主线程,如果所有工作线程在主线程调用Release之前完成)

答案 3 :(得分:1)

  1. 睡眠(N)(顺便提一下。在Sleep(0)上实现.Net自旋锁)
  2. 具有或不具有超时的WaitForSingleObject与Event或其他sync -objects一起使用

答案 4 :(得分:1)

你可以有一个计数器,每个任务在完成时自动递增(如前所述),但是也可以检查它是否是最后一个任务(计数器==总计),如果是,则设置单个事件。然后主线程只需要WaitForSingleObject()。只要确保支票也是原子地完成的。

// on task finished
Lock();
count++;
bool done = count == total;
Unlock();
if ( done )
    SetEvent( hEvent );

答案 5 :(得分:1)

我认为使用事件几乎是你最好的选择(也是最轻量级的)。我唯一要补充的是,在等待工作项目完成时,您可以使用以下方法简化代码:

HANDLE WorkEvents[ 5 ]; // store you event handles here

...

WaitForMultipleObjects(5, WorkEvents, TRUE, INFINITE);

这将等待所有事件在一次系统调用中完成。


编辑:另一种方法是,如果你不介意转动你的工作项,就是在每个线程上调用GetExitCodeThread,检查退出状态。

答案 6 :(得分:1)

您有几种选择:

  • 使用一个Semaphore而不是多个Event个实例 - 它支持计数锁定/等待。
  • 使用多个Critical Section个实例 - 它们比Event轻。
  • 使用工作线程中的InterlockedDecrement并在等待的线程句柄上旋转Wait...(使用短暂超时)循环。您必须根据自己的喜好调整超时值。此外,如果主线程是STA,请确保使用正确的Wait...方法来旋转消息循环。请注意,在某些情况下,您的等待也可能因APC事件而中断。
  • 使用其他答案中的任何技术在最后排队的工作线程上传输完成任务。请注意,仅当完成任务未绑定到主线程时,这才有用。