我使用遗留的QueueUserWorkItem函数汇总了一些工作项(我需要在vista之前支持操作系统,所以
for( <loop thru some items >)
{
QueueUserWorkItem( )
}
我需要等待这些才能继续下一步。 我见过几个类似的答案......但它们都在.NET中。 我正在考虑使用为每个项目存储一个事件并等待它们(喘气!),但是还有其他更好,轻量级的方法吗? (没有内核锁)
澄清:我知道使用事件。我对不需要内核级锁定的解决方案感兴趣。
答案 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.
void ThreadProc(void *context) {
MyCompletionTask *task = (MyCompletionTask)context;
// TODO: Do your thing here
task->Release();
}
使用此方法时要记住的一件事是完成任务完成的线程是非确定性的。它将取决于哪个工作线程首先完成(或主线程,如果所有工作线程在主线程调用Release之前完成)
答案 3 :(得分:1)
答案 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事件而中断。