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的定义。
- class JMutex
- {
- public:
- JMutex();
- ~JMutex();
- int Init();
- int Lock();
- int Unlock();
- bool IsInitialized() { return initialized; }
- private:
- #if (defined(WIN32) || defined(_WIN32_WCE))
- #ifdef JMUTEX_CRITICALSECTION
- CRITICAL_SECTION mutex; // 临界区对象
- #else // Use standard mutex
- HANDLE mutex; // 互斥内核对象
- #endif // JMUTEX_CRITICALSECTION
- #else // pthread mutex
- pthread_mutex_t mutex;
- #endif // WIN32
- bool initialized;
- };
其数据成员根据平台区分。对于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的定义。
- class JMutexAutoLock
- {
- public:
- JMutexAutoLock(JMutex &m) : mutex(m) { mutex.Lock(); }
- ~JMutexAutoLock() { mutex.Unlock(); }
- private:
- JMutex &mutex;
- };
JMutexAutoLock需要传入一个JMutex对象的引用来构造,当然,要求该JMutex对象已经初始化。在构造函数中调用mutex.Lock()上锁,在析构函数中mutex.Unlock()解锁。
JMutexAutoLock即所谓的自动锁,是对JMutex的自动管理,同步对象声明到对象生命周期结束之间的代码段(块)。它更容易实现线程安全,不用去担心什么时候为互斥体解锁。
2.3 JMutex示例
- JMutex mutex;
- mutex.Init();
- void DoSomeWork();
- void fun1()
- {
- mutex.Lock();
- mutex.Unlock();
- DoSomeWork();
- return;
- }
- void fun2()
- {
- mutex.Lock();
- DoSomeWork();
- mutex.Unlock();
- return;
- }
- void fun3()
- {
- JMutexAutoLock AutoLock(mutex);
- DoSomeWork();
- return;
- }
(1)在fun1()中,mutex.Lock();和mutex.Unlock();之间没有任何代码,则此处只是等待外部使用mutex保护的代码块运行完毕。如果外部占用该mutex,则此处等待;如果外部已释放该mutex,则此处继续执行DoSomeWork()。这里纯粹是等待外部事件发生:后上锁的等待先上锁的解锁。
(2)fun2()和fun3()是等价的,都是为了保护DoSomeWork()过程中涉及到的共享资源操作。
(3)在fun3()中,如果DoSomeWork()中途异常exit,则AutoLock不能正确析构,永远不会解锁。在此等待的后续线程(如果没有当掉)死锁。
3.JThread
下面是JThread类的定义。
- class JThread
- {
- public:
- JThread();
- virtual ~JThread();
- int Start();
- int Kill();
- virtual void *Thread() = 0;
- bool IsRunning();
- void *GetReturnValue();
- protected:
- void ThreadStarted();
- private:
- #if (defined(WIN32) || defined(_WIN32_WCE))
- #ifdef _WIN32_WCE
- DWORD threadid;
- static DWORD WINAPI TheThread(void *param);
- #else
- UINT threadid;
- static UINT __stdcall TheThread(void *param);
- #endif // _WIN32_WCE
- HANDLE threadhandle; //
- #else // pthread type threads
- pthread_t threadid;
- static void *TheThread(void *param);
- #endif // WIN32
- void *retval;
- bool running;
- JMutex runningmutex;
- JMutex continuemutex,
- continuemutex2;
- bool mutexinit;
- };
3.1JThread成员
threadid为线程ID号,threadhandle为线程内核对象句柄。
JThread类拥有三个JMutex对象成员,runningmutex、continuemutex和continuemutex2。bool mutexinit为三个对象初始化状态记录,只有三个互斥对象都成功初始化,才能协作完成后续对线程流程的正确控制。
TheThread(void *param)为通常意义上的线程入口函数,传递一个void*指针作为线程参数,该类静态入口传递JThread线程对象this指针。TheThread中调用Thread()完成特定的任务。我们姑且称TheThread()为线程壳(Shell),Thread()为线程核(Core)。
bool running为线程运行状态;bool IsRunning()为对该状态属性的访问。
void* retval为线程函数运行结果;void *GetReturnValue()为对该属性的访问。
3.2JThread类剖析
所谓同步是指多线程之间的同步,同一线程内部顺序执行不存在同步问题。JThread类中runningmutex、continuemutex、continuemutex2主要为了与它的创建线程同步。它的创建线程就是MyJThread对象实例声明代码所在的线程,也即调用JThread::Start()的父线程。注意线程壳TheThread()是为线程入口函数,其中调用MyJThread对象实例(this)的Thread(),线程壳TheThread()和线程核Thread()代码运行于新线程。因此,在Start()和TheThread()/Thread()间存在过程状态控制的同步问题。
更一般的同步问题体现在MyJThread对象实例声明代码所在的线程与新建线程关于running状态及返回值retval的访问。runningmutex互斥体主要用来保护running状态变量的访问,当然retval的访问与running状态密切相关,只有运行完才有返回值。
下面结合具体代码来分析Start()->TheThread()->Thread()的过程控制。
- int JThread::Start()
- {
- if (!mutexinit)
- {
- // 初始化3个JMutex(代码略)
- }
- runningmutex.Lock();
- if (running)
- {
- runningmutex.Unlock();
- return ERR_JTHREAD_ALREADYRUNNING;
- }
- runningmutex.Unlock();
- continuemutex.Lock();
- #ifndef _WIN32_WCE
- threadhandle = (HANDLE)_beginthreadex(NULL,0,TheThread,this,0,&threadid);
- #else
- threadhandle = CreateThread(NULL,0,TheThread,this,0,&threadid);
- #endif // _WIN32_WCE
- if (threadhandle == NULL)
- {
- continuemutex.Unlock();
- return ERR_JTHREAD_CANTSTARTTHREAD;
- }
- /* Wait until 'running' is set */
- runningmutex.Lock();
- while (!running)
- {
- runningmutex.Unlock();
- Sleep(1);
- runningmutex.Lock();
- }
- runningmutex.Unlock();
- continuemutex.Unlock();
- continuemutex2.Lock();
- continuemutex2.Unlock();
- return 0;
- }
- #ifndef _WIN32_WCE
- UINT __stdcall JThread::TheThread(void *param)
- #else
- DWORD WINAPI JThread::TheThread(void *param)
- #endif // _WIN32_WCE
- {
- JThread *jthread;
- void *ret;
- jthread = (JThread *)param; // this
- jthread->continuemutex2.Lock();
- jthread->runningmutex.Lock();
- jthread->running = true; // Thread is ready to run
- jthread->runningmutex.Unlock();
- jthread->continuemutex.Lock();
- jthread->continuemutex.Unlock();
- ret = jthread->Thread();
- jthread->runningmutex.Lock();
- jthread->running = false; // Thread run over
- jthread->retval = ret;
- CloseHandle(jthread->threadhandle); // Close Handle
- jthread->runningmutex.Unlock();
- return 0;
- }
- // Your own Thread implementation should call ThreadStarted immediately
- void JThread::ThreadStarted()
- {
- continuemutex2.Unlock();
- }
continuemutex互斥体用来同步等待新线程调度,具体来说Start()中调用_beginthreadex创建新线程之前即上锁。然后,在runningmutex的保护下等待running被线程壳TheThread()置为true,Start()中的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()中开辟一条线程,mutexinit、runningmutex、continuemutex、continuemutex2、running、retval等都是针对一次线程行为及状态的管理。如果在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包含一个RTPPollThread* pollthread成员。RTPPollThread为JRTPLIB中的RTP会话响应线程,继承自JThread类。
- class RTPPollThread : private JThread
- {
- public:
- RTPPollThread(RTPSession &session, RTCPScheduler &rtcpsched);
- ~RTPPollThread();
- int Start(RTPTransmitter *trans);
- void Stop();
- private:
- void *Thread();
- bool stop;
- JMutex stopmutex;
- RTPTransmitter *transmitter;
- RTPSession &rtpsession;
- RTCPScheduler &rtcpsched;
- };
- // 需要覆写实现的线程核
- void *RTPPollThread::Thread()
- {
- JThread::ThreadStarted();
- bool stopthread;
- stopmutex.Lock();
- stopthread = stop;
- stopmutex.Unlock();
- while (!stopthread)
- {
- int status;
- rtpsession.schedmutex.Lock();
- rtpsession.sourcesmutex.Lock();
- RTPTime rtcpdelay = rtcpsched.GetTransmissionDelay();
- rtpsession.sourcesmutex.Unlock();
- rtpsession.schedmutex.Unlock();
- if ((status = transmitter->WaitForIncomingData(rtcpdelay)) < 0)
- {
- stopthread = true;
- rtpsession.OnPollThreadError(status);
- }
- else
- {
- if ((status = transmitter->Poll()) < 0)
- {
- stopthread = true;
- rtpsession.OnPollThreadError(status);
- }
- else
- {
- if ((status = rtpsession.ProcessPolledData()) < 0)
- {
- stopthread = true;
- rtpsession.OnPollThreadError(status);
- }
- else
- {
- rtpsession.OnPollThreadStep();
- stopmutex.Lock();
- stopthread = stop;
- stopmutex.Unlock();
- }
- }
- }
- }
- return 0;
- }
RTPPollThread::Start()重载了基类的同名函数,作特定的初始化,调用JThread::Start()。RTPPollThread::Stop()对JThread::Kill()进行了安全扩展,如果等5秒后依旧JThread::IsRunning(),才调用JThread::Kill()强制关闭。
如果定义了RTP_SUPPORT_THREAD宏,则RTPSession支持多线程响应会话,usepollthread = true。在RTPSession::Create()中调用RTPSession::InternalCreate(),其中中创建线程(对象)。
- int RTPSession::InternalCreate(const RTPSessionParams &sessparams);
- {
- // Do thread stuff if necessary
- if (usingpollthread)
- {
- // ……
- pollthread = RTPNew(GetMemoryManager(),RTPMEM_TYPE_CLASS_RTPPOLLTHREAD) RTPPollThread(*this,rtcpsched);
- if (pollthread == 0)
- {
- // ……
- }
- if ((status = pollthread->Start(rtptrans)) < 0)
- {
- }
- }
- }
RTPPollThread::Thread()线程核处理具体的RTCP/RTP通信会话。在RTPSession::ProcessPolledData中调用RTPSessionSources::ProcessRawPacket。RTPSessionSources::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结构中的hEvent和hEvent2是怎么样同步实现线程控制的。
实际应用中,如果不需要过于严格的封装需求,仅需对线程参数(ID、HANDLE、THREADPROC、THREADPARAM)等做简单的封装,以期控制。例如Peercast中的classThreadInfo。
- // Peercast/core/common/sys.h
- #ifdef WIN32
- typedef int (WINAPI *THREAD_FUNC)(ThreadInfo*);
- typedef unsigned int THREAD_HANDLE;
- #endif
- class ThreadInfo
- {
- public:
- ThreadInfo()
- {
- active = false;
- id = 0;
- func = NULL;
- data = NULL;
- }
- void shutdown()
- {
- active = false;
- // sys->waitThread(this);
- }
- volatile bool active; // 是否有效
- THREAD_FUNC func; // 线程运行函数
- int id; // 线程ID
- THREAD_HANDLE handle; // 线程句柄
- void *data; // 线程参数
- };
- JTHREAD剖析
- Win32多线程编程(4) — JTHREAD剖析
- Win32多线程编程(4) — JTHREAD剖析
- JThread使用说明
- jthread库分析二:JThread类
- jrtplib+jthread 交叉编译
- 如何使用JTHREAD??求救
- 编译jrtplib和jthread
- jrtplib+jthread 交叉编译
- jrtplib+jthread 交叉编译
- 编译jrtp和jthread
- jrtplib+jthread 交叉编译
- 关于Jthread和JRTPlib
- jrtplib+jthread arm 交叉编译
- JRTPLIB和JThread简介翻译
- Compile JThread 4 Android On Ubuntu
- linux环境下编译 jrtplib 和 jthread
- Linux下编译jrtplib和jthread:
- Android 内存泄漏初探
- iOS 引入支付宝 缺少 #include <openssl/asn1.h>
- Java 8/26
- C语言和C++语言在语法上面的部分区别 (2
- 寒冰王座
- JTHREAD剖析
- android 打开各种文件(setDataAndType)
- 概率函数
- 产品开发需求
- Java多线程 -- 信号量
- Codeforces 235E Number Challenge (神定理+莫比乌斯反演)
- 下拉菜单及代码添加控件(菜鸟起步,大神勿喷,如有指教,洗耳恭听)
- POJ 2104 K-th Number
- java中的多生产者-消费者问题学习