2010-11-23 142 views
2

我有一个完全线程安全的FIFO结构(TaskList)来存储任务类,多线程数,其中一些创建和存储任务和其他处理的任务。 TaskList类有一个pop_front()方法,如果至少有一个返回第一个任务。否则它返回NULL
这里是处理功能的一个例子:等待的Win32线程

TaskList tlist; 

unsigned _stdcall ThreadFunction(void * qwe) 
{ 
    Task * task; 
    while(!WorkIsOver) // a global bool to end all threads. 
    { 
     while(task = tlist.pop_front()) 
     { 
      // process Task 
     } 
    } 
    return 0; 
} 

我的问题是,有时,存在在任务列表中没有新的任务,所以处理线程以无限循环(while(!WorkIsOver))和CPU负荷增大进入。不知怎的,我不得不让线程等待,直到一个新的任务存储在列表中。我考虑暂停和恢复,但是随后我需要额外的信息来说明哪些线程处于挂起或正在运行状态,从而给编码带来更大的复杂性。

任何想法?

PS。我正在使用winapi,而不是用于线程的Boost或TBB。因为有时我必须终止处理时间过长的线程,并立即创建新线程。这对我很重要。请不要建议这两个中的任何一个。

感谢

+0

Google“C++阻塞队列”。很多来自SO的点击。 – 2010-11-23 16:31:52

+1

终止线程会产生未定义的行为。如果你必须终止某些事情,那么创建进程并终止它们。 – Dialecticus 2010-11-23 17:07:15

+0

@Dialecitus:我认为一个进程是一个独立的应用程序,它是用命令行创建的。你有没有适合我的案例的例子? – 2010-11-23 23:25:47

回答

7

假设您在DevStudio中开发了这个工具,您可以使用[IO完成端口]获得所需的控件。可怕的名字,一个简单的工具。

  • 首先,创建一个IoCompletion端口:CreateIOCompletionPort
  • 创建工作线程池中使用_beginthreadex/CreateThread的
  • 在每个工作线程,实现循环调用GetQueuedCompletionStatus - 返回lpCompletionKey将指向要处理的工作项目。
  • 现在,无论何时您需要处理一个工作项目:从任何线程调用PostQueuedCompletionStatus - 将指针传递给您的工作项目作为完成键参数。

那就是它。 3个API调用,并且您已经实现了基于内核实现的队列对象的线程池机制。每次对PostQueuedCompletionStatus的调用都会自动反序列化到阻塞在GetQueuedCompletionStatus上的线程池线程。工作线程池由您创建并维护 - 因此您可以在任何耗时过长的工作线程上调用TerminateThread。甚至更好 - 取决于它如何设置,内核只会唤醒所需的线程数量,以确保每个CPU内核以〜100%的负载运行。

注意: TerminateThread实际上不是一个合适的API使用。除非你真的知道你在做什么,线程将会泄漏它们的堆栈,否则线程上的代码分配的内存将不会被释放,等等。 TerminateThread在进程关闭期间确实非常有用。网上有一些文章详细说明了如何释放每次调用TerminateThread时泄露的已知操作系统资源 - 如果坚持这种方法,如果您还没有这种方法,那么您确实需要查找并阅读它们。

1

使用条件变量来实现生产者/消费者队列 - 示例代码here

如果您需要支持早期版本的Windows,则可以在Boost中使用condition变量。或者,您可以通过从Boost头文件中复制特定于Windows的代码来构建自己的代码,它们在封面下使用相同的Win32 API,就像构建自己的代码一样。

2
  1. 在您的队列中使用信号量来指示是否有元素准备好进行处理。
  2. 你添加一个项目时,都会打电话::ReleaseSemaphore递增与信号
  3. 在你的线程进程相关联在循环计数,在你的信号灯对象的句柄调用::WaitForSingleObject() - 你可以给等待超时等等你有机会知道你的线程应该退出。否则,只要有一个或多个项目要处理时,线程就会被唤醒,并且还具有减少信号计数的好处。
0

为什么不直接使用现有的线程池?让Windows管理所有这些。

0
  1. 您可以使用Windows线程池!
  2. 或者你可以使用API​​调用 WaitForSingleObject的或 WaitForMultipleObjects的。
  3. 当线程无工作时,至少使用SwitchToThread api调用 。
0

如果任务列表具有某种wait_until_not_empty方法,然后使用它。如果没有,那么一个睡眠(1000)(或其他值)可能只是伎俩。正确的解决方案是创建一个TaskList的包装,它使用自动重置事件句柄来指示列表是否为空。您将需要彻底改造现有方法的流行/推,用新的任务列表是新类的成员:

WaitableTaskList::WaitableTaskList() 
{ 
    // task list is empty upon creation 
    non_empty_event = CreateEvent(NULL, FALSE, FALSE, NULL); 
} 

Task* WaitableTaskList::wait_and_pop_front(DWORD timeout) 
{ 
    WaitForSingleObject(non_empty_event, timeout); 
    // .. handle error, return NULL on timeout 

    Task* result = task_list.pop_front(); 

    if (!task_list.empty()) 
    SetEvent(non_empty_event); 

    return result; 
} 

void WaitableTaskList::push_back(Task* item) 
{ 
    task_list.push_back(item); 
    SetEvent(non_empty_event); 
} 

您只能通过诸如此wait_and_pop_front()弹出任务列表项。

编辑:实际上这不是一个好的解决方案。即使列表为空,也有办法让non_empty_event生成。情况需要2个线程试图弹出并列出2个项目。如果if和SetEvent之间的列表变空,我们将会有错误的状态。显然我们也需要实施同步。在这一点上,我会再次重新考虑简单的睡眠:-)

2

如果你还没有读过它,你应该吃草药萨特的Effective Concurrency系列,涵盖了这个话题,还有更多。