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 plain copy print ? 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的定義。
[cpp] view plain copy print ? 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樣本
[cpp] view plain copy print ? 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類的定義。
[cpp] view plain copy print ? 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()的過程式控制制。
[cpp] view plain copy print ? 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();