65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.95/5 (9投票s)

2006年11月21日

4分钟阅读

viewsIcon

89316

downloadIcon

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

实际上有两个不同的模板参数:TP;或者换句话说:类和参数。

要调用对象上的方法,编译器需要两样东西:对象的类型(模板参数列表中的 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 - 首次修订。
© . All rights reserved.