简体   繁体   English

等待win32线程

[英]Waiting win32 threads

I have a totally thread-safe FIFO structure( TaskList ) to store task classes, multiple number of threads, some of which creates and stores task and the others processes the tasks. 我有一个完全线程安全的FIFO结构( TaskList )来存储任务类,多个线程,其中一些创建和存储任务,其他线程处理任务。 TaskList class has a pop_front() method which returns the first task if there is at least one. TaskList类有一个pop_front()方法,如果至少有一个,则返回第一个任务。 Otherwise it returns NULL . 否则返回NULL
Here is an example of processing function: 这是一个处理函数的例子:

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;
}

My problem is, sometimes, there is no new task in the task list, so the processing threads enters in an endless loop ( while(!WorkIsOver) ) and CPU load increases. 我的问题是,有时候,任务列表中没有新任务,因此处理线程进入无限循环( while(!WorkIsOver) )并且CPU负载增加。 Somehow I have to make the threads wait until a new task is stored in the list. 不知何故,我必须让线程等待,直到新任务存储在列表中。 I think about Suspending and Resuming but then I need extra info about which threads are suspending or running which brings a greater complexity to coding. 我考虑暂停和恢复,但后来我需要关于哪些线程挂起或运行的额外信息,这给编码带来了更大的复杂性。

Any ideas? 有任何想法吗?

PS. PS。 I am using winapi, not Boost or TBB for threading. 我使用的是winapi,而不是Boost或TBB用于线程化。 Because sometimes I have to terminate threads that process for too long, and create new ones immediately. 因为有时我必须终止处理时间过长的线程,并立即创建新的线程。 This is critical for me. 这对我来说至关重要。 Please do not suggest any of these two. 请不要建议这两个中的任何一个。

Thanks 谢谢

Assuming you are developing this in DevStudio, you can get the control you want using [IO Completion Ports]. 假设您正在使用DevStudio进行开发,可以使用[IO完成端口]获得所需的控件。 Scary name, for a simple tool. 可怕的名字,一个简单的工具。

  • First, create an IOCompletion Port: CreateIOCompletionPort 首先,创建一个IOCompletion端口: CreateIOCompletionPort
  • Create your pool of worker threads using _beginthreadex / CreateThread 使用_beginthreadex / CreateThread创建工作线程池
  • In each worker thread, implement a loop that calls GetQueuedCompletionStatus - The returned lpCompletionKey will be pointing to a work item to process. 在每个工作线程中,实现一个调用GetQueuedCompletionStatus的循环 - 返回的lpCompletionKey将指向要处理的工作项。
  • Now, whenever you get a work item to process: call PostQueuedCompletionStatus from any thread - passing in the pointer to your work item as the completion key parameter. 现在,每当你得到一个要处理的工作项时:从任何线程调用PostQueuedCompletionStatus - 将指向工作项的指针作为完成键参数传递。

Thats it. 而已。 3 API calls and you have implemented a thread pooling mechanism based on a kernel implemented queue object. 3个API调用,您已经实现了基于内核实现的队列对象的线程池机制。 Each call to PostQueuedCompletionStatus will automatically be deserialized onto a thread pool thread thats blocking on GetQueuedCompletionStatus. 每次调用PostQueuedCompletionStatus都会自动反序列化到GetQueuedCompletionStatus上阻塞的线程池线程。 The pool of worker threads is created, and maintained - by you - so you can call TerminateThread on any worker threads that are taking too long. 工作线程池由您创建和维护 - 因此您可以在任何花费太长时间的工作线程上调用TerminateThread。 Even better - depending on how it is set up the kernel will only wake up as many threads as needed to ensure that each CPU core is running at ~100% load. 更好 - 根据它的设置方式,内核只会唤醒所需的线程,以确保每个CPU内核在~100%负载下运行。

NB. NB。 TerminateThread is really not an appropriate API to use. TerminateThread实际上不是一个合适的API。 Unless you really know what you are doing the threads are going to leak their stacks, none of the memory allocated by code on the thread will be deallocated and so on. 除非你真的知道你在做什么,否则线程会泄漏它们的堆栈,线程上的代码分配的内存都不会被释放,依此类推。 TerminateThread is really only useful during process shutdown. TerminateThread实际上只在进程关闭期间有用。 There are some articles on the net detailing how to release the known OS resources that are leaked each time TerminateThread is called - if you persist in this approach you really need to find and read them if you haven't already. 网上有一些文章详细说明了如何释放每次调用TerminateThread时泄露的已知操作系统资源 - 如果你坚持这种方法,你真的需要找到并阅读它们,如果你还没有。

  1. Use a semaphore in your queue to indicate whether there are elements ready to be processed. 在队列中使用信号量来指示是否有可以处理的元素。
  2. Every time you add an item, call ::ReleaseSemaphore to increment the count associated with the semaphore 每次添加项目时,请调用::ReleaseSemaphore以增加与信号量关联的计数
  3. In the loop in your thread process, call ::WaitForSingleObject() on the handle of your semaphore object -- you can give that wait a timeout so that you have an opportunity to know that your thread should exit. 在线程进程的循环中,在信号量对象的句柄上调用::WaitForSingleObject() - 您可以等待超时,以便您有机会知道您的线程应该退出。 Otherwise, your thread will be woken up whenever there's one or more items for it to process, and also has the nice side effect of decrementing the semaphore count for you. 否则,只要有一个或多个要处理的项目,您的线程就会被唤醒,并且还具有为您减少信号量计数的良好副作用。

如果你还没有阅读它,你应该吞噬Herb Sutter的有效并发系列,它涵盖了这个主题以及更多。

Use condition variables to implement a producer/consumer queue - example code here . 使用条件变量来实现生产者/消费者队列 - 这里是示例代码。

If you need to support earlier versions of Windows you can use the condition variable in Boost. 如果需要支持早期版本的Windows,可以在Boost中使用条件变量。 Or you could build your own by copying the Windows-specific code out of the Boost headers, they use the same Win32 APIs under the covers as you would if you build your own. 或者您可以通过从Boost标头中复制特定于Windows的代码来构建自己的代码,它们使用与构建自己的代码相同的Win32 API。

Why not just use the existing thread pool? 为什么不使用现有的线程池? Let Windows manage all of this. 让Windows管理所有这些。

  1. You can use windows threadpool! 你可以使用windows threadpool!
  2. Or you can use api call WaitForSingleObject or WaitForMultipleObjects. 或者您可以使用api调用WaitForSingleObject或WaitForMultipleObjects。
  3. Use at least SwitchToThread api call when thread is workless. 线程无效时至少使用SwitchToThread api调用。

If TaskList has some kind of wait_until_not_empty method then use it. 如果TaskList有某种wait_until_not_empty方法,那么使用它。 If it does not then one Sleep(1000) (or some other value) may just do the trick. 如果没有,那么一个Sleep(1000)(或其他一些值)可能就是这样做的。 Proper solution would be to create a wrapper around TaskList that uses an auto-reset event handle to indicate if list is not empty. 正确的解决方案是在TaskList周围创建一个包装器,它使用自动重置事件句柄来指示列表是否为空。 You would need to reinvent current methods for pop/push, with new task list being the member of new class: 您需要重新发明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);
}

You must pop items in task list only through methods such as this wait_and_pop_front() . 您必须仅通过此wait_and_pop_front()等方法在任务列表中弹出项目。

EDIT: actually this is not a good solution. 编辑:实际上这不是一个好的解决方案。 There is a way to have non_empty_event raised even if the list is empty. 即使列表为空,也有一种方法可以引发non_empty_event。 The situation requires 2 threads trying to pop and list having 2 items. 这种情况需要2个线程尝试弹出并列出有2个项目。 If list becomes empty between if and SetEvent we will have the wrong state. 如果if和SetEvent之间的列表变空,我们将具有错误的状态。 Obviously we need to implement syncronization as well. 显然我们也需要实现同步。 At this point I would reconsider simple Sleep again :-) 在这一点上,我会再次重新考虑简单的睡眠:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM