一个跨平台的 C++ 线程类






4.56/5 (24投票s)
2003 年 11 月 11 日
4分钟阅读

523726

1829
编写可在 Win32 和 Posix 兼容系统上无需修改即可工作的可移植面向对象线程。
引言
首先,本文的“先决条件”:我提供了一些关于使用互斥锁和信号量对象的简短示例,但我将确切的含义留给读者自行理解。此外,在阅读本文之前,您应该对多线程有深入的了解。
您可能会问:“这与其他线程类有什么不同?”首先,它是可移植的(但您可能已从标题中注意到)——但我最喜欢的部分是它高效。本文或我的代码中都不会使用 `new
`、`delete
`、`malloc`、`free` 等……所有操作都在堆栈上完成。我仍在努力使这个类更快,因此如果您喜欢这段代码,请时不时回来查看更新。
请告诉我您对本文和代码的看法,以及是否有任何建议可以改进它。
特点
- 纯虚接口,可轻松将任何类转换为线程类
- 线程句柄的类型抽象
- 支持平台之间无需代码更改
- 方便的静态函数用于创建静态线程
- 基本的便携式同步对象
- 在 Win32 (95-XP)、*Nix 和 SunOS >5.7 下测试过
- 应该可以在支持标准信号量的任何符合 Posix 标准的系统上运行
Thread 类
以下是用户可用的功能的快速概述
template < typename Thread_T > class Thread { public: /* just an old habit of mine */ typedef Thread_T & Thread_R; typedef const Thread_T & Thread_C_R; /* The handle type needed to use the auxiliary functions */ typedef /*Platform-Specific*/ Handle; /* The type of the function that would be called by the static */ /* Create() method, also the type of the pure virtual ThreadMain */ typedef void (/*VS: __cdecl*/ *Handler)( Thread_R ); /* Why is Thread_T a reference? because the parameter is */ /* copied before the thread function is called. */ /* There is a specialized version of this class for Thread_T */ /* of type void. In this version, the Handler function type */ /* has no parameter, and the create functions also take no */ /* parameters. I thought this would make things faster since */ /* there is no temporary objects or semaphores to deal with, */ /* but I experienced a slow-down for Thread<void>! Let me know */ /* if you happen to have insight into this. */ protected: /* This class is meant to be inherited, because it is useless */ /* in the sense of having instances, not to mention the */ /* pure virtual function... */ Thread(); /* Derived classes will have this function, and it will be called */ /* as a new thread. */ virtual void ThreadMain( Thread_R ) = 0; /* Static functions, for use within the thread */ /* Called from within the thread to return resources to the OS */ static void Exit(); /* POSIX only - for threads created with CancelAsync = false, */ /* tests to see if someone is trying to kill the current thread. */ /* does nothing in Win32. */ static void TestCancel(); /* Returns a handle to the current thread, or a pseudo-handle */ /* in Win32. Posix threads can Detach() themselves with this */ /* function, but you can't CloseHandle() on a pseudo-handle or */ /* a duplicated handle. */ static Handle Self(); public: /* The static thread creation function. */ /* useful when you don't need a class for your thread. */ /* Pretty self-explanatory. */ /* CreateDetached: if true, the thread's resources are automatically freed */ /* when it exits, but you cannot Join() it. */ /* CancelEnable: if true, this thread can be Kill()'ed */ /* CancelAsync: if true, The thread is terminated immediately upon Kill() */ /* being called. if false, it will only exit when TestCancel() is called. */ /* Return Value: returns 0 on success, and errno on fail. */ /* H is set to the handle (or InvalidHandle on fail) if not zero. */ static int Create( const Handler & Function, Thread_C_R Param, Handle * const & H = 0, const bool & CreateDetached = false, const unsigned int & StackSize = 0, const bool & CancelEnable = false, // UNUSED in Win32 const bool & CancelAsync = false // UNUSED in Win32 ); /* The in-class creation function. Same params, minus function ptr. */ /* Same return value, too. */ /* Thread<void>::Create() functions do not take a Param */ int Create( Thread_C_R Param, Handle * const & H = 0, const bool & CreateDetached = false, const unsigned int & StackSize = 0, const bool & CancelEnable = false, // UNUSED in Win32 const bool & CancelAsync = false // UNUSED in Win32 ) const; /* Wait for thread with handle H to complete */ /* returns EAGAIN, EINVAL on error or zero on success. */ static int Join( Handle H ); /* Forcefully terminate the thread (win32) or cancel the thread (posix). */ /* A better way to end your thread is through some kind of external sync */ /* mechanism. */ /* returns zero on success or EINVAL */ static int Kill( Handle H ); /* Allow the system to reclaim the thread's resources when it is done. */ /* Join()ing will not work anymore (posix), but you can devise another */ /* mechanism if you like, maybe using mutexes. This closes the handle in Win32. */ /* returns zero on success or EINVAL */ static int Detach( Handle H ); };
正如您所见,我在此类中坚持了基本功能。某些函数在 Win32 上未实现,但您仍然可以像使用它们一样使用它们,而无需担心代码质量,这样您就可以了解线程在非 Windows 平台上的具体运行方式。
正如我之前所说,我在创建线程时没有使用任何虚拟内存。相反,我使用了信号量和一个临时对象
int Create( ... ) { Semaphore S; Temp_Object T( [ref to] S, [ref to] Parameter, this ); if ( Call_To_OS_Thread_Create(Static_Function,(void *)&T) Succeeds ) { /* wait for the thread to signal the semaphore */ S.Wait(); /* now we know that the thread function is done playing */ /* with the stack */ return OK; } return FAILED; } static void Static_Function( void *T ) { Thread *DestObj(((Temp_Object *)T)->ClassPtr); Thread_T Copy_Of_Parameter(((Temp_Object *)T)->ParamRef); /* cuases the create function to stop waiting */ ((Temp_Object *)T)->Sem->Post(); /* now we can call the overloaded thread function for DestObj */ }
如伪代码所示,`create` 函数可以安全地将其堆栈上的对象指针传递过去,因为它不会退出,直到线程函数使用完它为止。`create` 函数对对象也很友好,因为它们只复制 `Thread_T` 一次。如果您是经验丰富的模板编写者,您可能会注意到,当 `Thread_T` 是引用类型时,该类的布局会导致问题。要解决此问题,只需创建一个简单的 `struct
`,其中包含一个引用作为其值,您就可以恢复正常工作了。仅此一个快速提示:希望您已经熟悉互斥锁和信号量,正如我上面建议的那样。我还包含了一些非常简单的特定于平台的类,名为 `Semaphore` 和 `Mutex`,它们具有一些基本的函数,行为符合预期。对于 `Mutex`,这些函数是:`Lock`、`Unlock` 和 `Lock_Try`(适用于 >= Win98)。对于 `Semaphore`,它们是:`Reset(value)`、`Post`(即 `Release`)、`Wait`、`Wait_Try` 和 `Value`。请注意,`Mutex` 类在 Win32 中实际上不是互斥锁,而是临界区,它比进程间互斥锁快得多。
用法
我希望这些信息以及上面对 `Thread` 成员函数的详细描述足以让您熟悉。
/* Example1.cpp In this example, I will show you how to write your own class to define your own thread function that is tied to an object, along with one way to use a semaphore and a mutex. Win32: Make sure to set "C/C++" -> "Code Generation" -> "Runtime Library" to - Multi-Threaded Debug, or - Multi-Threaded Solaris, Linux, Unix, [Posix Compliant]: compile as g++ Example1.cpp /usr/lib/libthread.so sometimes it's libpthread.so... try find /usr/lib -n "lib*thread*.so" */ #include <iostream> using namespace std; #include <time.h> #include "Thread.h" #include "Mutex.h" int ii = 0; /* the mutex that all threads will use */ Mutex ii_lock; /* the semaphore that the Run() function waits on */ Semaphore sem_done; const static int rep = 1000; /* Our class that has Thread<int> as a base class - int is the parameter type. */ class ThreadTester : private Thread<int> { public: void Run() { /* create rep #threads with parameter i. */ /* if this class was private Thread<void>, there would not be a */ /* parameter. */ /* real code should check for errors from Create(). */ for ( int i = 0; i < rep; ++i ) Create(i,0,true,2048); /* when sem_done.Post() is called below, this wait completes */ /* and also decrements the value of sem_done back to zero. */ sem_done.Wait(); /* another way to do this might be to keep a list of all */ /* the thread handles and call Join() for each one, */ /* but I chose this way since the number of threads is pre-determined. */ } private: /* the thread entry function, which is part of this object. */ /* for Thread<void>, there would be no parameter. */ /* i is a reference because it is copied in the _real_ thread */ /* initialization, and so this just points to it. handy for */ /* structs and things other than integral data types. */ void ThreadMain(int &i) { /* get mutually exclusive access to ii */ ii_lock.Lock(); ++ii; /* if this is the last created thread, increment the value */ /* of sem_done to 1, which causes Run() to complete. */ if ( ii >= rep ) sem_done.Post(); /* let someone else pass ii_lock */ ii_lock.Unlock(); } }; int main() { clock_t c1, c2; ThreadTester Test; /* just to test efficiency. */ c1 = clock(); Test.Run(); c2 = clock(); /* you will notice a MAJOR speed increase from win32 to solaris. */ /* I havent run this example on anything else, but I assume that */ /* win32 threads are either less efficient or the creation process */ /* is more "processor-friendly" since win32's aim isn't to create */ /* fast but to run fast (if that's really possible for win32 :P ) */ /* I get about ~200 for my winXP 850MHz Pentium III-M - */ /* clocked about !!!12000!!! for SunOS 5.8 on (I believe) 4 processors. */ /* single processor solaris - can't remeber results at the moment. */ cout << (double)rep/((double)(c2-c1)/(double)CLOCKS_PER_SEC) << " Threads per second!" << endl; #ifdef WIN32 system("pause"); #endif return 0; } /* Example2.cpp In this example, I will show you how to use the static thread functions instead of writing your own class, along with how to explicitly wait for a single thread to complete. Win32: Make sure to set "C/C++" -> "Code Generation" -> "Runtime Library" to - Multi-Threaded Debug, or - Multi-Threaded Solaris, Linux, Unix, [Posix Compliant]: compile as g++ Example1.cpp /usr/lib/libthread.so sometimes it's libpthread.so... try find /usr/lib -n "lib*thread*.so" */ #include <iostream> using namespace std; #include "Thread.h" /* non-class thread function */ void MyThreadProc( int &I ) { for ( int i = 0; i < I; ++i ); } /* Our parameter type is int */ typedef Thread<int> Thread_Int; int main() { Thread_Int::Handle H; /* I had to specify the explicit type of the function in order */ /* to force the calling of the static overload. */ if ( Thread_Int::Create((Thread_Int::Handler)MyThreadProc,100000000,&H) ) { cout << "Creating of thread failed!\n"; } else { cout << "Waiting...\n"; /* this returns when the thread completes */ Thread_Int::Join(H); cout << "Thread is done.\n"; } #ifdef WIN32 system("pause"); #endif return 0; }
我确信……
……关于线程、互斥锁、信号量和同步,我还可以深入探讨很多内容,但我选择将本文列为中级,并希望读者已经阅读了 CP 的“线程、进程与 IPC”部分足够多的内容,了解正在发生的事情。我的目标是向您展示一种使用此特定类进行简单可移植多线程的机制——我希望我在这方面取得了成功,并期待您的反馈。
更新
- 2003/11/17 更新
- 另一个 Win32 调整 - 将默认创建函数设置为 `CreateThread`(在检查了 `_beginthread` 代码后)。您仍然可以指定其他方法……有关详细信息,请参阅 _Win32_/_Thread.h_。(顺便说一句,对于那些速度狂来说,它会稍微快一些:))
- 2003/11/16 更新
- 从 `Thread<>::Handler` 类型中删除了 `__stdcall`——不必要(Win32)
- 2003/11/12 更新
- 为 Win32 `Detach()` 添加了功能——关闭线程的句柄。不幸的是,线程无法像 Posix 线程那样分离自己,因此最佳用途是在创建时或在线程外部。
- 添加了 `Self()` 函数,该函数返回线程本身的句柄。在 Win32 中仅为伪句柄。
- 通过使用相同的信号量来同步所有线程的创建,以及一个静态互斥锁,进一步提高了速度。
- 显式将静态线程初始化函数指定为 `__stdcall`(Win32)
- 将 `StackSize` 参数添加到 `create` 函数
- 使用 `_beginthreadex`/`_endthreadex` 而不是 `_beginthread`/`_endthread`(Win32)