检查线程是否完成的正确方法是什么?

6

我在我的应用程序中使用了多线程和 _beginthread。目前为止,为了等待所有的线程完成,我使用全局布尔变量,每个线程完成后都会将其设置为 true,然后我进入 while 循环等待。一定有更简洁的方法来实现这个吧?

谢谢

6个回答

8

如果您只有线程ID,需要通过调用OpenThread将其转换为HANDLE。 - rustyx
2
不要调用OpenThread!如果线程已经退出并且ID被重新分配,你可能会得到一个指向不同随机线程的句柄。使用_beginthreadex,它返回线程的句柄。(_beginthread也返回一个句柄,但是(引用文档)它"可能无效或指向另一个线程"。永远不要使用_beginthread。) - benrg

3
通常的方法是保留所有线程句柄,然后等待每个句柄。当句柄被“信号”时,线程已经完成,因此它将从线程集合中移除。我使用std::set<HANDLE>来跟踪线程句柄。在Windows上有两种不同的等待多个对象的方法:
  1. 遍历集合并对每个对象调用WaitForSingleObject
  2. 将集合转换为数组或向量,并调用WaitForMultipleObjects
第一种方法听起来效率低下,但实际上它是这两种方法中最直接和最少出错的方法。如果您需要等待所有线程,则使用以下循环:
std::set<HANDLE> thread_handles; // contains the handle of each worker thread
while (!thread_handles.empty()) {
    std::set<HANDLE> threads_left;
    for (std::set<HANDLE>::iterator cur_thread=thread_handles.begin(),
                                    last=thread_handles.end();
         cur_thread != last; ++cur_thread)
    {
        DWORD rc = ::WaitForSingleObject(*cur_thread, some_timeout);
        if (rc == WAIT_OBJECT_0) {
            ::CloseHandle(*cur_thread); // necessary with _beginthreadex
        } else if (rc == WAIT_TIMEOUT) {
            threads_left.add(cur_thread); // wait again
        } else {
            // this shouldn't happen... try to close the handle and hope
            // for the best!
            ::CloseHandle(*cur_thread); // necessary with _beginthreadex
        }
    }
    std::swap(threads_left, thread_handles);
}

使用WaitForMultipleObjects等待线程完成比听起来要困难一些。以下代码将等待所有线程完成;但是,它每次只等待WAIT_MAXIMUM_OBJECTS个线程。另一个选择是循环遍历每个页面的线程。我将把这个练习留给读者 ;)
DWORD large_timeout = (5 * 60 * 1000); // five minutes
std::set<HANDLE> thread_handles; // contains the handle of each worker thread
std::vector<HANDLE> ary;         // WaitForMultipleObjects wants an array...
while (!thread_handles.empty()) {
    ary.assign(thread_handles.begin(), thread_handles.end());
    DWORD rc = ::WaitForMultipleObjects(std::min(ary.size(), WAIT_MAXIMUM_OBJECTS),
                                        &ary[0], FALSE, large_timeout);
    if (rc == WAIT_FAILED) {
        // handle a failure case... this is usually something pretty bad
        break;
    } else if (rc == WAIT_TIMEOUT) {
        // no thread exited in five minutes... this can be tricky since one of
        // the threads beyond the first WAIT_MAXIMUM_OBJECTS may have terminated
    } else {
        long idx = (rc - WAIT_OBJECT_0);
        if (idx > 0 && idx < ary.size()) {
            // the object at `idx` was signaled, this means that the
            // thread has terminated.
            thread_handles.erase(ary[idx]);
            ::CloseHandle(ary[idx]); // necessary with _beginthreadex
        }
    }
}

这并不是非常美观,但应该可以工作。 如果您相信所有线程都将退出并且不介意等待它们,那么可以使用 WaitForMultipleObjects(ary.size(), &ary[0], TRUE, INFINITE)。 通常情况下,这样做并不安全,因为一个失控的线程会导致您的应用程序无限期地阻塞,而且仅在 ary.size() 小于 MAXIMUM_WAIT_OBJECTS 时才有效。
当然,另一种选择是找到一个线程池实现并使用它。编写线程代码实际上并不是很有趣,特别是一旦您不得不在实际环境中支持它。考虑使用 boost::thread_group 等类似工具。

3
你需要了解的是线程同步技术 - 幸运的是,在MSDN上有很多信息可以帮助你。你可能想要使用事件和等待句柄,以下是主要内容: http://msdn.microsoft.com/en-us/library/ms681924%28v=VS.85%29.aspx。还有一些关于MFC的同步信息(仅供参考):http://msdn.microsoft.com/en-us/library/975t8ks0%28VS.71%29.aspx

我进行了一些搜索,但很难找到一些有用的信息,这些信息不使用MFC实现。这里有一个很好的教程(http://www.informit.com/library/content.aspx?b=Visual_C_PlusPlus&seqNum=149),但同样使用了MFC。你可以先看看互斥锁的MFC实现。

因此,你需要熟悉同步函数和结构 - 这些都在MSDN上有详细介绍:http://msdn.microsoft.com/en-us/library/ms686679%28v=VS.85%29.aspx


你一定要使用事件而不是布尔值。 - DanDan

3
使用 _beginthreadex 替代。无论是 _beginthread 还是 _beginthreadex 都返回线程句柄,但是由 _beginthread 启动的线程在完成后会自动关闭其句柄,因此在同步方面使用它不可靠。
线程句柄可与 Win32 的同步函数之一一起使用,例如 WaitForSingleObjectWaitForMultipleObjects
完成后,_beginthreadex 返回的句柄必须使用 CloseHandle() 关闭。

1
你可以使用boost::thread对象。调用对象上的join,它将等待线程完成。

0

Windows 提供了一种线程间通知的事件机制。Visual C++ 默认只在 MFC 中提供对事件的支持。如果需要一个可移植的、非 MFC 版本的事件机制,可以查看 Boost 库中的 线程管理类。它们使得启动和等待线程变得更加容易,尽管它们并不直接提供对所有 Windows API 功能的访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接