JTHREAD剖析

来源:互联网 发布:怎么联系淘宝店主 编辑:程序博客网 时间:2024/05/17 07:42

1.JTHREAD介绍

实际项目中经常会涉及到多线程架构。为了给WINUX(Windows+Linux)平台提供一套相同的操作线程的接口,需要将平台上对线程操作的API封装成一个的通用类。JTHREAD即是这样的一个开源类库。

JTHREAD是很简单的,主要包含JThread类和JMutex类,它们分别代表一个线程和一个互斥体,互斥体是为了同步多线程通信。该开发包作了简单的跨平台实现,对于*NIX平台调用pthread库,对于Windows平台调用Win32 Theads库。

本文基于JTHREAD库源码做简单剖析,以对前面线程控制、线程同步等议题实做演练。

按照惯例,对于返回int类型值的函数,若返回大于等于0的值表示成功,负值表示出错。

 

2.JMutex

2.1 JMutex

下面是类JMutex的定义。

[cpp] view plaincopyprint?
  1. class JMutex  
  2. {  
  3. public:  
  4.     JMutex();  
  5.     ~JMutex();  
  6.   
  7.     int Init();  
  8.     int Lock();  
  9.     int Unlock();  
  10.     bool IsInitialized() { return initialized; }  
  11.   
  12. private:  
  13.   
  14. #if (defined(WIN32) || defined(_WIN32_WCE))  
  15. #ifdef JMUTEX_CRITICALSECTION  
  16.     CRITICAL_SECTION mutex; // 临界区对象  
  17. #else // Use standard mutex  
  18.     HANDLE mutex;           // 互斥内核对象  
  19. #endif // JMUTEX_CRITICALSECTION  
  20. #else // pthread mutex  
  21.     pthread_mutex_t mutex;  
  22. #endif // WIN32  
  23.   
  24.     bool initialized;  
  25. };  

        其数据成员根据平台区分。对于Windows系统(Win32或WinCE),若定义JMUTEX_CRITICALSECTION宏,则使用临界区对象CRITICAL_SECTION作为互斥体;否则,使用互斥内核对象(Mutex)作为互斥体。对于*NIX系统使用pthread_mutex_t作为互斥体。后面主要针对Windows系统解说,鉴于临界区旋转锁的高效性,建议使用JMUTEX_CRITICALSECTION

int Init()完成互斥体的初始化;bool initialized跟踪初始化记录,确保只初始化一次。可调用bool IsInitialized()获取initialized的值,以判断是否已经初始化。

int Init()中,如果定义了JMUTEX_CRITICALSECTION宏,则调用InitializeCriticalSection(&mutex)初始化临界区;否则mutex = CreateMutex(NULL,FALSE,NULL),创建互斥内核对象。

Lock()/Unlock()为同步加解锁操作。Lock()内部体现为EnterCriticalSection(&mutex)进入临界区,或WaitForSingleObject(mutex,INFINITE)返回,可拥有互斥对象。Unlock()内部体现为LeaveCriticalSection(&mutex)离开临界区,或ReleaseMutex(mutex)释放拥有的互斥对象。

构造函数JMutex()中初始化initialized = false;析构函数~JMutex()中DeleteCriticalSection(&mutex)删除临界区或CloseHandle(mutex)关闭互斥内核对象,释放互斥体资源。

在你使用一个JMutex类的实例对象之前,你首先必须调用Init()函数执行初始化。通过检测IsInitialized()的返回值可以检测互斥体是否已经初始化。初始化之后,通过调用Lock()和Unlock()封闭需要同步的共享资源操作代码段。

2.2 JMutexAutoLock

下面是类JMutexAutoLock的定义。

[cpp] view plaincopyprint?
  1. class JMutexAutoLock  
  2. {  
  3. public:  
  4.     JMutexAutoLock(JMutex &m) : mutex(m) { mutex.Lock(); }  
  5.     ~JMutexAutoLock() { mutex.Unlock(); }  
  6.   
  7. private:  
  8.     JMutex &mutex;  
  9. };  

JMutexAutoLock需要传入一个JMutex对象的引用来构造,当然,要求该JMutex对象已经初始化。在构造函数中调用mutex.Lock()上锁,在析构函数中mutex.Unlock()解锁。

JMutexAutoLock即所谓的自动锁,是对JMutex的自动管理,同步对象声明到对象生命周期结束之间的代码段(块)。它更容易实现线程安全,不用去担心什么时候为互斥体解锁。

2.3 JMutex示例

[cpp] view plaincopyprint?
  1. JMutex mutex;  
  2. mutex.Init();  
  3.   
  4. void DoSomeWork();  
  5.   
  6. void fun1()  
  7. {  
  8.     mutex.Lock();  
  9.     mutex.Unlock();  
  10.     DoSomeWork();  
  11.   
  12.     return;  
  13. }  
  14.   
  15. void fun2()  
  16. {  
  17.     mutex.Lock();  
  18.     DoSomeWork();  
  19.     mutex.Unlock();  
  20.       
  21.     return;  
  22. }  
  23.   
  24. void fun3()  
  25. {  
  26.     JMutexAutoLock AutoLock(mutex);  
  27.     DoSomeWork();  
  28.   
  29.     return;  
  30. }  

(1)在fun1()中,mutex.Lock();和mutex.Unlock();之间没有任何代码,则此处只是等待外部使用mutex保护的代码块运行完毕。如果外部占用该mutex,则此处等待;如果外部已释放该mutex,则此处继续执行DoSomeWork()。这里纯粹是等待外部事件发生:后上锁的等待先上锁的解锁

(2)fun2()和fun3()是等价的,都是为了保护DoSomeWork()过程中涉及到的共享资源操作。

(3)在fun3()中,如果DoSomeWork()中途异常exit,则AutoLock不能正确析构,永远不会解锁。在此等待的后续线程(如果没有当掉)死锁。

 

3.JThread

下面是JThread类的定义。

[cpp] view plaincopyprint?
  1. class JThread  
  2. {  
  3. public:  
  4.     JThread();  
  5.     virtual ~JThread();  
  6.   
  7.     int Start();  
  8.     int Kill();  
  9.     virtual void *Thread() = 0;  
  10.     bool IsRunning();  
  11.     void *GetReturnValue();  
  12.   
  13. protected:  
  14.     void ThreadStarted();  
  15.   
  16. private:  
  17.   
  18. #if (defined(WIN32) || defined(_WIN32_WCE))  
  19. #ifdef _WIN32_WCE  
  20.     DWORD threadid;  
  21.     static DWORD WINAPI TheThread(void *param);  
  22. #else  
  23. UINT threadid;  
  24. static UINT __stdcall TheThread(void *param);     
  25. #endif // _WIN32_WCE  
  26.     HANDLE threadhandle; //   
  27. #else // pthread type threads  
  28.     pthread_t threadid;  
  29.     static void *TheThread(void *param);  
  30. #endif // WIN32  
  31.   
  32.     void *retval;  
  33.     bool running;  
  34.       
  35.     JMutex runningmutex;  
  36.     JMutex continuemutex,  
  37.         continuemutex2;  
  38.   
  39.     bool mutexinit;  
  40. };  

3.1JThread成员

threadid为线程ID号,threadhandle为线程内核对象句柄。

JThread类拥有三个JMutex对象成员,runningmutexcontinuemutexcontinuemutex2bool mutexinit为三个对象初始化状态记录,只有三个互斥对象都成功初始化,才能协作完成后续对线程流程的正确控制。

TheThread(void *param)为通常意义上的线程入口函数,传递一个void*指针作为线程参数,该类静态入口传递JThread线程对象this指针。TheThread中调用Thread()完成特定的任务。我们姑且称TheThread()为线程壳(Shell),Thread()为线程核(Core)。

bool running为线程运行状态;bool IsRunning()为对该状态属性的访问。

void* retval为线程函数运行结果;void *GetReturnValue()为对该属性的访问。

3.2JThread类剖析

所谓同步是指多线程之间的同步,同一线程内部顺序执行不存在同步问题。JThread类中runningmutexcontinuemutexcontinuemutex2主要为了与它的创建线程同步。它的创建线程就是MyJThread对象实例声明代码所在的线程,也即调用JThread::Start()的线程。注意线程壳TheThread()是为线程入口函数,其中调用MyJThread对象实例(this)的Thread(),线程壳TheThread()和线程核Thread()代码运行于线程。因此,在Start()和TheThread()/Thread()间存在过程状态控制的同步问题。

更一般的同步问题体现在MyJThread对象实例声明代码所在的线程与新建线程关于running状态及返回值retval的访问。runningmutex互斥体主要用来保护running状态变量的访问,当然retval的访问与running状态密切相关,只有运行完才有返回值。

下面结合具体代码来分析Start()->TheThread()->Thread()的过程控制。

[cpp] view plaincopyprint?
  1. int JThread::Start()  
  2. {  
  3.     if (!mutexinit)  
  4.     {  
  5.         // 初始化3个JMutex(代码略)  
  6.     }  
  7.       
  8.     runningmutex.Lock();  
  9.     if (running)  
  10.     {  
  11.         runningmutex.Unlock();  
  12.         return ERR_JTHREAD_ALREADYRUNNING;  
  13.     }  
  14.     runningmutex.Unlock();  
  15.       
  16.     continuemutex.Lock();  
  17.       
  18. #ifndef _WIN32_WCE  
  19.     threadhandle = (HANDLE)_beginthreadex(NULL,0,TheThread,this,0,&threadid);  
  20. #else  
  21.     threadhandle = CreateThread(NULL,0,TheThread,this,0,&threadid);  
  22. #endif // _WIN32_WCE  
  23.       
  24.     if (threadhandle == NULL)  
  25.     {  
  26.         continuemutex.Unlock();  
  27.         return ERR_JTHREAD_CANTSTARTTHREAD;  
  28.     }  
  29.       
  30.     /* Wait until 'running' is set */  
  31.     runningmutex.Lock();  
  32.     while (!running)  
  33.     {  
  34.         runningmutex.Unlock();  
  35.         Sleep(1);  
  36.         runningmutex.Lock();  
  37.     }  
  38.     runningmutex.Unlock();  
  39.       
  40.     continuemutex.Unlock();  
  41.       
  42.     continuemutex2.Lock();  
  43.     continuemutex2.Unlock();  
  44.       
  45.     return 0;  
  46. }  
  47.   
  48. #ifndef _WIN32_WCE  
  49. UINT __stdcall JThread::TheThread(void *param)  
  50. #else  
  51. DWORD WINAPI JThread::TheThread(void *param)  
  52. #endif // _WIN32_WCE  
  53. {  
  54.     JThread *jthread;  
  55.     void *ret;  
  56.       
  57.     jthread = (JThread *)param; // this  
  58.       
  59.     jthread->continuemutex2.Lock();  
  60.       
  61.     jthread->runningmutex.Lock();  
  62.     jthread->running = true// Thread is ready to run  
  63.     jthread->runningmutex.Unlock();  
  64.       
  65.     jthread->continuemutex.Lock();  
  66.     jthread->continuemutex.Unlock();  
  67.       
  68.     ret = jthread->Thread();  
  69.       
  70.     jthread->runningmutex.Lock();  
  71.     jthread->running = false// Thread run over  
  72.     jthread->retval = ret;  
  73.     CloseHandle(jthread->threadhandle); // Close Handle  
  74.     jthread->runningmutex.Unlock();  
  75.       
  76.     return 0;  
  77. }  
  78.   
  79. // Your own Thread implementation should call ThreadStarted immediately  
  80. void JThread::ThreadStarted()  
  81. {  
  82.     continuemutex2.Unlock();  
  83. }  

continuemutex互斥体用来同步等待新线程调度,具体来说Start()中调用_beginthreadex创建新线程之前即上锁。然后,在runningmutex的保护下等待running被线程壳TheThread()置为trueStart()中的continuemutex才解锁,TheThread()继而执行线程核Thread()。

新线程一调度(Start),即进入线程壳TheThread(),continuemutex2互斥体即上锁;Start()中continuemutex解锁,但仍未返回,还需等待continuemutex2解锁。在线程核Thread()中需立即调用ThreadStarted()解锁continuemutex2,此时Start()返回。也即在实际进入线程核Thread()执行时,Start()才返回。

由以上分析可知:

  • runningmutex:父线程等待新线程调度,新线程调度时会设置信号量Signal(running = true),即runningmutex互斥体用来保护running状态变量的同步读写。
  • continuemutex:新线程等待父线程获知自己已调度运行,即continuemutex互斥体同步的是线程的创建到线程被调度(线程壳真正启动,running=true)过程。
  • continuemutex2:父线程等待新线程核执行,即continuemutex2互斥体同步的是线程被调度到线程核真正执行过程(Thread()->ThreadStarted())。此时,父线程真正启动了新线程(而不仅仅是创建成功),Start()才返回。

3.3JThread类的使用说明

因为含有未实现的纯虚函数virtual void *Thread() = 0,故JThread为抽象基类,无法直接声明创建JThread对象实例。在使用时,必须编写派生类实现Thread()接口,以完成特定的任务:classMyJThread : publicJThread这样,一个MyJThread类实际上只能完成一种特定的任务。如以上代码所述,往往为线程壳TheThread()传递JThread对象的this指针,以便线程核Thread()能访问派生类实例对象属性。记得在你自己的Thread()实现中实时调用ThreadStarted()使父线程Start返回。

一个MyJThread对象管理完成特定任务的一个线程对象,其行为具有不可重入性。意即当MyJThread::Start()中开辟一条线程,mutexinitrunningmutexcontinuemutexcontinuemutex2runningretval等都是针对一次线程行为及状态的管理。如果在Start()没有返回之前,或者线程过程没有返回之前,试图再次调用该实例的Start()进行新线程的创建,则上述一套设施服务于两个线程对象,则容易造成管理上错乱。实际上JThread::Start()已经对运行状态作了检测,连续调用Start(),将导致ERR_JTHREAD_ALREADYRUNNING错误。当然Start()后,确保运行结束,可再次Start()开辟新的线程,以完成同类多任务,但此时已经丧失了多线程并发的初衷,因为实际上这里是一个线程跑完,才开另一个线程。

对于同类多任务,往往声明创建多个MyJThread对象实例,然后Start()。在理想情况下,让线程核Thread()、线程壳TheThread()自然返回,以使其寿终正寝。迫不得已,可调用Kill()杀死线程。Kill()调用的是线程终结者TerminateThread(),如前所述,这种粗暴的行径将导致不良的后果,除非拥有此线程的进程终止运行,否则系统不会销毁这个线程的堆栈。当然,在继承的MyJThread,往往需要改进Kill()操作,以便作更优雅的控制退出。

记住,一个MyJThread对象对应一个线程对象,你每Start一个MyJThread实例,就相当于创建一个线程。当然,只要你的MyJThread扩展到足够强壮,你也可以将同一级别的不同类任务在Thread()中作统一处理,这取决于你的业务分工强度。

Start()理应让Thread()自然返回,Start()后通过调用IsRunning()函数可以检测线程是否在运行;若运行完毕(running=false),则可通过调用GetReturnValue()函数可以获取返回值。最后,你可以通过Kill()函数中止一个正在运行的线程。对于一个已经返回的线程,Kill()调用返回ERR_JTHREAD_NOTRUNNING

3.4JThread应用实例:JRTPLIB中的RTPPollThread

JTRPLIB中的RTP会话类RTPSession包含一个RTPPollThreadpollthread成员。RTPPollThread为JRTPLIB中的RTP会话响应线程,继承自JThread类。

[cpp] view plaincopyprint?
  1. class RTPPollThread : private JThread  
  2. {  
  3. public:  
  4.     RTPPollThread(RTPSession &session, RTCPScheduler &rtcpsched);  
  5.     ~RTPPollThread();  
  6.     int Start(RTPTransmitter *trans);  
  7.     void Stop();  
  8. private:  
  9.     void *Thread();  
  10.       
  11.     bool stop;  
  12.     JMutex stopmutex;  
  13.     RTPTransmitter *transmitter;  
  14.       
  15.     RTPSession &rtpsession;  
  16.     RTCPScheduler &rtcpsched;  
  17. };  
  18. // 需要覆写实现的线程核  
  19. void *RTPPollThread::Thread()  
  20. {  
  21.     JThread::ThreadStarted();  
  22.       
  23.     bool stopthread;  
  24.       
  25.     stopmutex.Lock();  
  26.     stopthread = stop;  
  27.     stopmutex.Unlock();  
  28.     while (!stopthread)  
  29.     {  
  30.         int status;  
  31.           
  32.         rtpsession.schedmutex.Lock();  
  33.         rtpsession.sourcesmutex.Lock();  
  34.           
  35.         RTPTime rtcpdelay = rtcpsched.GetTransmissionDelay();  
  36.           
  37.         rtpsession.sourcesmutex.Unlock();  
  38.         rtpsession.schedmutex.Unlock();  
  39.           
  40.         if ((status = transmitter->WaitForIncomingData(rtcpdelay)) < 0)  
  41.         {  
  42.             stopthread = true;  
  43.             rtpsession.OnPollThreadError(status);  
  44.         }  
  45.         else  
  46.         {  
  47.             if ((status = transmitter->Poll()) < 0)  
  48.             {  
  49.                 stopthread = true;  
  50.                 rtpsession.OnPollThreadError(status);  
  51.             }  
  52.             else  
  53.             {  
  54.                 if ((status = rtpsession.ProcessPolledData()) < 0)  
  55.                 {  
  56.                     stopthread = true;  
  57.                     rtpsession.OnPollThreadError(status);  
  58.                 }  
  59.                 else  
  60.                 {  
  61.                     rtpsession.OnPollThreadStep();  
  62.                     stopmutex.Lock();  
  63.                     stopthread = stop;  
  64.                     stopmutex.Unlock();  
  65.                 }  
  66.             }  
  67.         }  
  68.     }  
  69.       
  70.     return 0;  
  71. }  

RTPPollThread::Start()重载了基类的同名函数,作特定的初始化,调用JThread::Start()RTPPollThread::Stop()对JThread::Kill()进行了安全扩展,如果等5秒后依旧JThread::IsRunning(),才调用JThread::Kill()强制关闭。

如果定义了RTP_SUPPORT_THREAD宏,则RTPSession支持多线程响应会话,usepollthread = true。在RTPSession::Create()中调用RTPSession::InternalCreate(),其中中创建线程(对象)。

[cpp] view plaincopyprint?
  1. int RTPSession::InternalCreate(const RTPSessionParams &sessparams);  
  2. {  
  3.     // Do thread stuff if necessary  
  4.     if (usingpollthread)  
  5.     {  
  6.         // ……  
  7.         pollthread = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPOLLTHREAD) RTPPollThread(*this,rtcpsched);  
  8.         if (pollthread == 0)  
  9.         {  
  10.             // ……  
  11.         }  
  12.         if ((status = pollthread->Start(rtptrans)) < 0)  
  13.         {  
  14.         }  
  15.     }  
  16. }  

  RTPPollThread::Thread()线程核处理具体的RTCP/RTP通信会话。在RTPSession::ProcessPolledData中调用RTPSessionSources::ProcessRawPacketRTPSessionSources::ProcessRawPacket中判断包的类型是RTCP还是RTP,若是RTCP包,则ProcessRTCPCompoundPacketàOnRTCPCompoundPacket处理;若是RTP包,则ProcessRTPPacketàOnRTPPacket处理,从而完成RTCP/RTP通信。

关于JRTPLIB的使用,参考《JRTPLIB@Conference DIY视频会议系统》。

 

4.CWinThread简介

CWinThread作为MFC的线程管理类,极具参考性,源码参阅Microsoft Visual Studio/VC98/MFC/SRC/THRDCORE.CPP。尽管其采用了面向对象的封装,但在实作时,通常按照_beginthreadex的方式调用AfxBeginThread传入线程函数地址和线程参数。内部对CWinThread对象作了自动化的管理。重点关注_AFX_THREAD_STARTUP结构中的hEventhEvent2是怎么样同步实现线程控制的。

实际应用中,如果不需要过于严格的封装需求,仅需对线程参数(ID、HANDLE、THREADPROC、THREADPARAM)等做简单的封装,以期控制。例如Peercast中的classThreadInfo

[cpp] view plaincopyprint?
  1. // Peercast/core/common/sys.h  
  2. #ifdef WIN32  
  3. typedef int (WINAPI *THREAD_FUNC)(ThreadInfo*);  
  4. typedef unsigned int THREAD_HANDLE;  
  5. #endif  
  6.   
  7. class ThreadInfo  
  8. {  
  9. public:  
  10.     ThreadInfo()  
  11.     {  
  12.         active = false;  
  13.         id = 0;  
  14.         func = NULL;  
  15.         data = NULL;  
  16.     }  
  17.   
  18.     void shutdown()  
  19.     {  
  20.         active = false;  
  21.         // sys->waitThread(this);  
  22.     }  
  23.   
  24.     volatile bool active;  // 是否有效  
  25.     THREAD_FUNC func;      // 线程运行函数  
  26.     int id;                    // 线程ID  
  27.     THREAD_HANDLE handle;  // 线程句柄  
  28.   
  29.     void *data;              // 线程参数  
  30. };  
0 0
原创粉丝点击