等待win32线程

时间:2010-11-23 16:26:02

标签: c++ multithreading winapi synchronization

我有一个完全线程安全的FIFO结构(TaskList)来存储任务类,多个线程,其中一些创建并存储任务,其他线程处理任务。 TaskList类有pop_front()方法,如果至少有一个,则返回第一个任务。否则返回NULL 以下是处理函数的示例:

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用于线程化。因为有时我必须终止处理时间过长的线程,并立即创建新的线程。这对我来说至关重要。请不要建议这两个中的任何一个。

由于

7 个答案:

答案 0 :(得分:7)

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

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

多数民众赞成。 3个API调用,您已经实现了基于内核实现的队列对象的线程池机制。每次调用PostQueuedCompletionStatus都会自动反序列化到GetQueuedCompletionStatus上阻塞的线程池线程。工作线程池由您创建和维护 - 因此您可以在任何花费太长时间的工作线程上调用TerminateThread。更好 - 根据它的设置方式,内核只会唤醒所需的线程,以确保每个CPU内核在~100%负载下运行。

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

如果您还没有阅读过,那么您应该吞噬Herb Sutter的Effective Concurrency系列,该系列涵盖了这个主题以及更多内容。

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

  1. 你可以使用windows threadpool!
  2. 或者您可以使用api通话 WaitForSingleObject或 WaitForMultipleObjects的。
  3. 至少使用SwitchToThread api调用 当线程无法使用时。

答案 6 :(得分:0)

如果TaskList有某种wait_until_not_empty方法,那么就使用它。如果没有,那么一个Sleep(1000)(或其他一些值)可能就是这样做的。正确的解决方案是在TaskList周围创建一个包装器,它使用自动重置事件句柄来指示列表是否为空。您需要重新发明pop / push的当前方法,新任务列表是新类的成员:

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之间的列表变空,我们将具有错误的状态。显然我们也需要实现同步。在这一点上,我会再次重新考虑简单的睡眠: - )