C++ 等待工作项使用QueueUserWorkItem(非.NET)完成池化

C++ 等待工作项使用QueueUserWorkItem(非.NET)完成池化,c++,winapi,multithreading,C++,Winapi,Multithreading,我使用传统的QueueUserWorkItem函数汇集了一些工作项(我需要支持vista之前的OSs,所以 for( <loop thru some items >) { QueueUserWorkItem( ) } () { QueueUserWorkItem() } 在进行下一步之前,我需要等待这些。 我已经看到了几个类似的答案…但它们都在.NET中。 我正在考虑使用一个存储每个项目的事件并等待它们(gasp!),但是还有其他更好的、轻量级的方法吗?(没有内核锁) 澄

我使用传统的QueueUserWorkItem函数汇集了一些工作项(我需要支持vista之前的OSs,所以

for( <loop thru some items >)
{
    QueueUserWorkItem( )
}
() { QueueUserWorkItem() } 在进行下一步之前,我需要等待这些。 我已经看到了几个类似的答案…但它们都在.NET中。 我正在考虑使用一个存储每个项目的事件并等待它们(gasp!),但是还有其他更好的、轻量级的方法吗?(没有内核锁)

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

  • Sleep(N)(顺便说一下,在Sleep(0)上实现.Net自旋锁)
  • WaitForSingleObject带或不带超时与事件或其他同步对象一起使用

  • 这样做的唯一方法是在每个任务完成时设置一个互锁的计数器

    然后你可以做一个

    while( counter < total )
        Sleep( 0 );
    
    while(计数器<总数)
    睡眠(0);
    
    任务的一部分可以向事件(或其他同步对象)发送信号,您可以执行以下操作

    while( count < total )
        WaitForSingleObject( hEvent, INFINITE );
    
    while(计数<总数)
    WaitForSingleObject(hEvent,无限);
    
    第二种方法意味着主线程使用更少的处理时间


    编辑:TBH避免内核锁的唯一方法是旋转锁,这意味着您将有一个内核浪费时间,而这些时间本来可以用来处理您的工作队列(或其他任何事情)。如果确实必须避免内核锁,则使用睡眠旋转锁(0)但是,我绝对建议只使用内核锁,将额外的CPU时间用于有意义的处理,并停止担心“非”问题。

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

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

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

    HANDLE WorkEvents[ 5 ]; // store you event handles here
    
    ...
    
    WaitForMultipleObjects(5, WorkEvents, TRUE, INFINITE);
    
    这将等待所有事件在一次系统调用中完成



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

    如果您实际拆分执行堆栈,则不需要等待:直到在调用线程上执行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
      }
    }
    
    调用方函数(…) { sharedCounter=; 对于() { QueueUserWorkItem(WorkerFunction,…); } 退出线程;(返回,无等待逻辑) } WorkerFunction(,…) { //在此处执行排队项 计数器=联锁计数器(共享计数器); if(0==计数器) { //这是最后一个线程,所有其他线程都已完成 在此处继续调用调用函数的原始逻辑 } }
    这是否有效取决于许多因素,如果可以在调用线程上暂停其执行并在队列线程上恢复,我不能说我们不了解调用方上下文。顺便说一句,“退出线程”并不是指线程突然中止,而是一个优雅的返回,整个调用堆栈都准备好移交e队列线程的执行上下文。我认为这不是一项简单的任务。

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

    将“完成”任务实现为引用计数对象。每个工作线程在执行其工作时持有对此对象的引用,然后在完成时将其释放。完成任务在引用计数达到0时执行其工作

    例子 注意:我的C++经过几年主要在C语言中的工作,生锈了,所以把下面的例子当作伪代码< /p>来处理。 完成任务 呼叫码
    这种方法需要记住的一点是,完成任务的线程是不确定的。它将取决于哪个工作线程首先完成(或者主线程,如果所有工作线程都在主线程调用Release之前完成)

    您有几种选择:

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

    +1:我们使用一种非常相似的模式,除了我们使用单个内核事件来表示所有工作程序都已完成。@Filip:是的,最后一个线程发出事件信号,调用者将唤醒并继续。正如OP所要求的,我上面提出的完全无事件,代价是“切换”执行上下文的复杂性。事实上,这是“switc”h’只是一个伪装的异步回调调用,所以并不难。如果不是自动选择答案,我会选择它(但这不会给我带来同样的问题吗?我一直在调用线程中等待完成任务。如果我没有得到它,很抱歉。使用这种方法,您不会在主线程中进行等待。在完成任务中,您将在所有工作线程完成后放入要运行的代码,并且在最后一次调用时将调用该代码。)释放被调用。信号量将
    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.
    
    void ThreadProc(void *context) {
    
        MyCompletionTask *task = (MyCompletionTask)context;
    
        //  TODO: Do your thing here
    
        task->Release();
    }