【问题标题】:Waiting win32 threads等待win32线程
【发布时间】:2010-11-29 17:36:52
【问题描述】:

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

谢谢

【问题讨论】:

  • 谷歌“C++ 阻塞队列”。来自 SO 的大量热门歌曲。
  • 终止线程会产生未定义的行为。如果您必须终止某些东西,请创建进程并终止它们。
  • @Dialecitus:我认为一个进程是一个单独的应用程序,它是用命令行创建的。你有什么适合我的例子吗?

标签: c++ multithreading winapi synchronization


【解决方案1】:

假设你是在 DevStudio 中开发的,你可以使用 [IO Completion Ports] 来获得你想要的控制。可怕的名字,一个简单的工具。

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

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

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

【讨论】:

  • 这正是我所需要的。但是让我问一个问题。为什么你认为这比 bgporter 的信号量、waitforsingleobject 等更好?显然 GetQueuedCompletionStatus 有一个内置的等待结构,或者它甚至可以使用 WaitForSingleObject。
  • 使用 bgporter 方法的原因是:了解这样的事情是如何实现的总是有用的。使用 GetQueuedCompletionStatus 的原因:它是一个高性能的内置实现,已经过广泛调试,并且它实现了用户模式代码中不可能实现的功能(并发计数)。
  • 感谢您对 I/O 完成端口的出色解释。现在我明白了这种动物是什么,它的用途,以及它是如何工作的!现在我看到了,它非常简单。为什么 MSDN 不能解释它为什么存在。
【解决方案2】:
  1. 在队列中使用信号量来指示是否有元素准备好进行处理。
  2. 每次添加项目时,调用::ReleaseSemaphore 以增加与信号量相关的计数
  3. 在您的线程进程的循环中,在您的信号量对象的句柄上调用::WaitForSingleObject()——您可以让该等待超时,以便您有机会知道您的线程应该退出。否则,只要有一个或多个项目要处理,您的线程就会被唤醒,并且还会为您减少信号量计数。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

        • 因为它的最低要求是客户端的Vista。
        【解决方案6】:
        1. 你可以使用windows线程池!
        2. 或者你可以使用api调用 WaitForSingleObject 或 WaitForMultipleObjects。
        3. 至少使用 SwitchToThread api 调用 当线程不工作时。

        【讨论】:

          【解决方案7】:

          如果 TaskList 有某种 wait_until_not_empty 方法,则使用它。如果没有,那么一个 Sleep(1000) (或其他值)可能就可以解决问题。正确的解决方案是围绕 TaskList 创建一个包装器,该包装器使用 auto-reset 事件句柄来指示列表是否为空。您需要重新发明当前的 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 之间变为空,我们将有错误的状态。显然我们也需要实现同步。在这一点上,我会再次重新考虑简单的 Sleep :-)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-02-04
            • 1970-01-01
            • 2023-03-11
            • 2011-08-13
            • 1970-01-01
            相关资源
            最近更新 更多