C++ 中的高效线程——第 1 部分:基本线程类






3.95/5 (9投票s)
2006年11月21日
4分钟阅读

89316

1967
将 Win32 线程 API 封装到一个 C++ 友好的类中。
引言
在过去的几年里,多线程应用程序已经成为我开发工具库的支柱。我甚至发现自己有时会编写不需要多线程的应用程序。工作线程可以让 Win32 应用程序提升用户体验并提高性能。
对于 C++ 程序和程序员来说,Windows API 的 C 接口并不一定能很好地融入新的或现有的类层次结构中。然而,线程概念已成为并行编程实践的支柱,并且在越来越广泛的场景中变得越来越适用。
在本系列三篇文章的第一部分中,我将介绍我的通用线程类,解释其背后的设计决策以及如何使用它来实现最大的效果。
Thread 类
在深入之前,让我们先看看 Thread
类的公共接口。
class Thread { public: Thread(); virtual ~Thread(); // Suspend - Suspends the thread (if one is active) void Suspend(); // Resume - Resumes a previously suspended thread void Resume(); // Terminate - Terminates the thread (if one is active). // Prefer another means of exiting the thread, as // calling Terminate() does not allow the thread to free // any resources it may hold void Terminate(); // IsThreadActive - Called in the context of another // (external) thread to test whether the thread is // currently running bool IsThreadActive() const; };
这个相当基础的接口也相当简洁。它以一种简洁、 C++ 友好的类方式暴露了 Win32 API 在线程本身上的许多操作。
现在,您可能会自言自语:“嘿?你说这是一个完整的接口。里面甚至没有创建线程的方法!” 啊,我们正在朝着那个方向前进。但在此之前,我们必须经历模板的领域。
没错,我的 Thread
类的实现使用了一些模板技巧。这些技巧是必需的,以便我们可以跨不同的客户端类使用相同的代码。毕竟,您不想为每个客户端重写这个类,对吧?
那么,让我们更仔细地看看源代码中的几行。
template<class T, class P> class Thread { public: typedef void (T::*ThreadFunc)( P ); // ... // Same as above // ... };
实际上有两个不同的模板参数:T
和 P
;或者换句话说:类和参数。
要调用对象上的方法,编译器需要两样东西:对象的类型(模板参数列表中的 T
)以及方法的签名(由返回类型、类、方法名称和参数列表组成)。对于这个实现,假设方法不返回任何值(void
)并且接受一个参数(有关不进行这些假设的更通用解决方案,请参阅本系列的第 3 篇文章)。
typedef
使事情变得非常容易。这是一个方法 typedef
,指定 ThreadFunc
是一个指向函数指针的函数指针,该函数接受 T
的一个 P
作为参数并且不返回任何内容。您明白 typedef
为什么能让事情变容易吗?
现在我们已经推拉着穿过了模板的领域,让我们来看看 Thread
类的核心:Run()
方法。
// Run - Start the Thread and run the method // pClass->(*pfFunc), passing p as an argument. // Returns true if the thread was created // successfully, false otherwise bool Run( T* pClass, ThreadFunc pfFunc, P p );
就是这样!这就是 Thread
类的公共接口。并不复杂。实现也同样简单。那么,我们如何使用这个新类呢?
使用 Thread 类
让我们来看一个如何使用 Thread
类的示例。
class TestClass { ... private: Lib::Thread<TestClass, int> m_thread; };
在这个示例中,Thread
类应该作为另一个类的成员变量使用(稍作修改,该类也可以作为独立对象使用;我将把这个留给读者作为令人头疼的练习……目前)。封闭类是 Thread
类的第一个模板参数。这是编译器调用正确类中的正确方法所必需的。第二个模板参数是要传递给线程方法的参数类型。
要启动线程,请调用 Thread::Run()
方法。
m_thread.Run( this, &TestClass::DoWork, i );
这将使用标准的 Win32 ::CreateThread(...)
方法创建一个新线程。这个新线程会立即调用 Thread::Run()
方法中指定的那个方法(我知道它可能看起来不像,但我们正在所有这些参数中指定一个函数)。
Thread::Run()
方法的三个参数是:
- 我们要调用方法的对象
- 我们要调用的方法
- 我们要传递给方法的参数
我们需要什么来指定一个函数调用?从编译器的角度来看,它们是返回类型、类、方法名称和参数列表。从运行时的角度来看,我们还需要一个对象。由于返回类型是硬编码的,而类和参数列表在模板参数中指定,因此编译器只需要方法名称,运行时则需要一个对象。您猜怎么着?这些就是 Thread::Run()
方法的前两个参数(分别是参数 2 和 1)。
线程被调用后,它(基本上)会立即调用:
this->DoWork( i );
一旦该方法退出,线程就完成了工作,然后也随之退出。
结论
Thread
类相当基础,但非常有用。它为强大的 Win32 C 线程 API 提供了 C++ 包装器。此特定实现做出了一些假设:Thread
类必须是封闭类的成员,返回类型必须是 void
,并且该方法接受单个参数。
这些假设在当前可能不太理想,因此第 3 篇文章将向您展示如何使用更多模板技巧来绕过这些假设。(相信我,不会那么痛苦。)
但首先,第 2 篇文章将向您展示如何将 Thread
类扩展到我所见过的最有用的线程范例。
历史
- 2006.11.21 - 首次修订。