Linux下通用線程池的建立與使用(C++)
來源:互聯網
上載者:User
本文給出了一個通用的線程池架構,該架構將與線程執行相關的任務進行了高層次的抽象,使之與具體的執行任務無 關。另外該線程池具有動態伸縮性,它能根據執行任務的輕重自動調整線程池中線程的數量。文章的最後,我們給出一個簡單樣本程式,通過該樣本程式,我們會發 現,通過該線程池架構執行多線程任務是多麼的簡單。
為什麼需要線程池 目前的大多數網路伺服器,包括Web伺服器、Email伺服器以及資料庫伺服器等都具有一個共同點,就是單位時間內必須處理數目巨大的串連請求,但處理時間卻相對較短。 傳統多線程方案中我們採用的伺服器模型則是一旦接受到請求之後,即建立一個新的線程,由該線程執行任務。任務執 行完畢後,線程退出,這就是是“即時建立,即時銷毀”的策略。儘管與建立進程相比,建立線程的時間已經大大的縮短,但是如果提交給線程的任務是執行時間較 短,而且執行次數極其頻繁,那麼伺服器將處於不停的建立線程,銷毀線程的狀態。 我們將傳統方案中的線程執行過程分為三個過程:T1、T2、T3。 T1:線程建立時間 T2:線程執行時間,包括線程的同步等時間 T3:線程銷毀時間 那麼我們可以看出,線程本身的開銷所佔的比例為(T1+T3) / (T1+T2+T3)。如果線程執行的時間很短的話,這比開銷可能佔到20%-50%左右。如果任務執行時間很頻繁的話,這筆開銷將是不可忽略的。 除此之外,線程池能夠減少建立的線程個數。通常線程池所允許的並發線程是有上界的,如果同時需要並發的線程數超 過上界,那麼一部分線程將會等待。而傳統方案中,如果同時請求數目為2000,那麼最壞情況下,系統可能需要產生2000個線程。儘管這不是一個很大的數 目,但是也有部分機器可能達不到這種要求。 因此線程池的出現正是著眼於減少線程池本身帶來的開銷。線程池採用預建立的技術,在應用程式啟動之後,將立即建立一定數量的線程(N1),放入空閑隊列中。這些線程都是處於阻塞(Suspended)狀態,不消耗CPU,但佔用較小的記憶體空間。當任務到來後,緩衝池選擇一個空閑線程,把任務傳入此線程中運行。當N1個線程都在處理任務後,緩衝池自動建立一定數量的新線程,用於處理更多的任務。在任務執行完畢後線程也不退出,而是繼續保持在池中等待下一次的任務。當系統比較空閑時,大部分線程都一直處於暫停狀態,線程池自動銷毀一部分線程,回收系統資源。 基於這種預建立技術,線程池將線程建立和銷毀本身所帶來的開銷分攤到了各個具體的任務上,執行次數越多,每個任務所分擔到的線程本身開銷則越小,不過我們另外可能需要考慮進去線程之間同步所帶來的開銷。
構建線程池架構 一般線程池都必須具備下面幾個組成部分: 線程池管理器:用於建立並管理線程池 背景工作執行緒: 線程池中實際執行的線程 任務介面: 儘管線程池大多數情況下是用來支援網路伺服器,但是我們將線程執行的任務抽象出來,形成任務介面,從而是的線程池與具體的任務無關。 任務隊列:線程池的概念具體到實現則可能是隊列,鏈表之類的資料結構,其中儲存執行線程。 我們實現的通用線程池架構由五個重要部分組成CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外架構中還包括線程同步使用的類CThreadMutex和CCondition。 CJob是所有的任務的基類,其提供一個介面Run,所有的任務類都必須從該類繼承,同時實現Run方法。該方法中實現具體的任務邏輯。 CThread是Linux中線程的封裝,其封裝了Linux線程最經常使用的屬性和方法,它也是一個抽象類別,是所有線程類的基類,具有一個介面Run。 CWorkerThread是實際被調度和執行的線程類,其從CThread繼承而來,實現了CThread中的Run方法。 CThreadPool是線程池類,其負責儲存線程,釋放線程以及調度線程。 CThreadManage是線程池與使用者的直接介面,其屏蔽了內部的具體實現。 CThreadMutex用於線程之間的互斥。 CCondition則是條件變數的封裝,用於線程之間的同步。 它們的類的繼承關係如下圖所示: 線程池的時序很簡單,如下圖所示。CThreadManage直接跟用戶端打交道,其接受需要建立的線程初始個 數,並接受用戶端提交的任務。這兒的任務是具體的非抽象的任務。CThreadManage的內部實際上調用的都是CThreadPool的相關操作。 CThreadPool建立具體的線程,並把用戶端提交的任務分發給CWorkerThread,CWorkerThread實際執行具體的任務。
理解系統組件 下面我們分開來瞭解系統中的各個組件。
CThreadManage CThreadManage的功能非常簡單,其提供最簡單的方法,其類定義如下: class CThreadManage { private: CThreadPool* m_Pool; int m_NumOfThread; protected: public: void SetParallelNum(int num); CThreadManage(); CThreadManage(int num); virtual ~CThreadManage(); void Run(CJob* job,void* jobdata); void TerminateAll(void); }; 其中m_Pool指向實際的線程池;m_NumOfThread是初始建立時候允許建立的並發的線程個數。另外Run和TerminateAll方法也非常簡單,只是簡單的調用CThreadPool的一些相關方法而已。其具體的實現如下: CThreadManage::CThreadManage(){ m_NumOfThread = 10; m_Pool = new CThreadPool(m_NumOfThread); } CThreadManage::CThreadManage(int num){ m_NumOfThread = num; m_Pool = new CThreadPool(m_NumOfThread); } CThreadManage::~CThreadManage(){ if(NULL != m_Pool) delete m_Pool; } void CThreadManage::SetParallelNum(int num){ m_NumOfThread = num; } void CThreadManage::Run(CJob* job,void* jobdata){ m_Pool->Run(job,jobdata); } void CThreadManage::TerminateAll(void){ m_Pool->TerminateAll(); }
CThread CThread 類實現了對Linux中線程操作的封裝,它是所有線程的基類,也是一個抽象類別,提供了一個抽象介面Run,所有的CThread都必須實現該Run方法。CThread的定義如下所示: class CThread { private: int m_ErrCode; Semaphore m_ThreadSemaphore; //the inner semaphore, which is used to realize unsigned long m_ThreadID; bool m_Detach; //The thread is detached bool m_CreateSuspended; //if suspend after creating char* m_ThreadName; ThreadState m_ThreadState; //the state of the thread protected: void SetErrcode(int errcode){m_ErrCode = errcode;} static void* ThreadFunction(void*); public: CThread(); CThread(bool createsuspended,bool detach); virtual ~CThread(); virtual void Run(void) = 0; void SetThreadState(ThreadState state){m_ThreadState = state;} bool Terminate(void); //Terminate the threa bool Start(void); //Start to execute the thread void Exit(void); bool Wakeup(void); ThreadState GetThreadState(void){return m_ThreadState;} int GetLastError(void){return m_ErrCode;} void SetThreadName(char* thrname){strcpy(m_ThreadName,thrname);} char* GetThreadName(void){return m_ThreadName;} int GetThreadID(void){return m_ThreadID;} bool SetPriority(int priority); int GetPriority(void); int GetConcurrency(void); void SetConcurrency(int num); bool Detach(void); bool Join(void); bool Yield(void); int Self(void); }; 線程的狀態可以分為四種,空閑、忙碌、掛起、終止(包括正常退出和非正常退出)。由於目前Linux線程庫不支 持掛起操作,因此,我們的此處的掛起操作類似於暫停。如果線程建立後不想立即執行任務,那麼我們可以將其“暫停”,如果需要運行,則喚醒。有一點必須注意 的是,一旦線程開始執行任務,將不能被掛起,其將一直執行任務至完畢。 線程類的相關操作均十分簡單。線程的執行入口是從Start()函數開始,其將調用函數ThreadFunction,ThreadFunction再調用實際的Run函數,執行實際的任務。
CThreadPool CThreadPool是線程的承載容器,一般可以將其實現為堆棧、單向隊列或者雙向隊列。在我們的系統中我們使用STL Vector對線程進行儲存。CThreadPool的實現代碼如下: class CThreadPool { friend class CWorkerThread; private: unsigned int m_MaxNum; //the max thread num that can create at the same time unsigned int m_AvailLow; //The min num of idle thread that shoule kept unsigned int m_AvailHigh; //The max num of idle thread that kept at the same time unsigned int m_AvailNum; //the normal thread num of idle num; unsigned int m_InitNum; //Normal thread num; protected: CWorkerThread* GetIdleThread(void); void AppendToIdleList(CWorkerThread* jobthread); void MoveToBusyList(CWorkerThread* idlethread); void MoveToIdleList(CWorkerThread* busythread); void DeleteIdleThread(int num); void CreateIdleThread(int num); public: CThreadMutex m_BusyMutex; //when visit busy list,use m_BusyMutex to lock and unlock CThreadMutex m_IdleMutex; //when visit idle list,use m_IdleMutex to lock and unlock CThreadMutex m_JobMutex; //when visit job list,use m_JobMutex to lock and unlock CThreadMutex m_VarMutex; CCondition m_BusyCond; //m_BusyCond is used to sync busy thread list CCondition m_IdleCond; //m_IdleCond is used to sync idle thread list CCondition m_IdleJobCond; //m_JobCond is used to sync job list CCondition m_MaxNumCond; vector<CWorkerThread*> m_ThreadList; vector<CWorkerThread*> m_BusyList; //Thread List vector<CWorkerThread*> m_IdleList; //Idle List CThreadPool(); CThreadPool(int initnum); virtual ~CThreadPool(); void SetMaxNum(int maxnum){m_MaxNum = maxnum;} int GetMaxNum(void){return m_MaxNum;} void SetAvailLowNum(int minnum){m_AvailLow = minnum;} int GetAvailLowNum(void){return m_AvailLow;} void SetAvailHighNum(int highnum){m_AvailHigh = highnum;} int GetAvailHighNum(void){return m_AvailHigh;} int GetActualAvailNum(void){return m_AvailNum;} int GetAllNum(void){return m_ThreadList.size();} int GetBusyNum(void){return m_BusyList.size();} void SetInitNum(int initnum){m_InitNum = initnum;} int GetInitNum(void){return m_InitNum;} void TerminateAll(void); void Run(CJob* job,void* jobdata); }; CThreadPool::CThreadPool() { m_MaxNum = 50; m_AvailLow = 5; m_InitNum=m_AvailNum = 10 ; m_AvailHigh = 20; m_BusyList.clear(); m_IdleList.clear(); for(int i=0;i<m_InitNum;i++){ CWorkerThread* thr = new CWorkerThread(); thr->SetThreadPool(this); AppendToIdleList(thr); thr->Start(); } } CThreadPool::CThreadPool(int initnum) { assert(initnum>0 && initnum<=30); m_MaxNum = 30; m_AvailLow = initnum-10>0?initnum-10:3; m_InitNum=m_AvailNum = initnum ; m_AvailHigh = initnum+10; m_BusyList.clear(); m_IdleList.clear(); for(int i=0;i<m_InitNum;i++){ CWorkerThread* thr = new CWorkerThread(); AppendToIdleList(thr); thr->SetThreadPool(this); thr->Start(); //begin the thread,the thread wait for job } } CThreadPool::~CThreadPool() { TerminateAll(); } void CThreadPool::TerminateAll() { for(int i=0;i < m_ThreadList.size();i++) { CWorkerThread* thr = m_ThreadList[i]; thr->Join(); } return; } CWorkerThread* CThreadPool::GetIdleThread(void) { while(m_IdleList.size() ==0 ) m_IdleCond.Wait(); m_IdleMutex.Lock(); if(m_IdleList.size() > 0 ) { CWorkerThread* thr = (CWorkerThread*)m_IdleList.front(); printf("Get Idle thread %d/n",thr->GetThreadID()); m_IdleMutex.Unlock(); return thr; } m_IdleMutex.Unlock(); return NULL; } //add an idle thread to idle list void CThreadPool::AppendToIdleList(CWorkerThread* jobthread) { m_IdleMutex.Lock(); m_IdleList.push_back(jobthread); m_ThreadList.push_back(jobthread); m_IdleMutex.Unlock(); } //move and idle thread to busy thread void CThreadPool::MoveToBusyList(CWorkerThread* idlethread) { m_BusyMutex.Lock(); m_BusyList.push_back(idlethread); m_AvailNum--; m_BusyMutex.Unlock(); m_IdleMutex.Lock(); vector<CWorkerThread*>::iterator pos; pos = find(m_IdleList.begin(),m_IdleList.end(),idlethread); if(pos !=m_IdleList.end()) m_IdleList.erase(pos); m_IdleMutex.Unlock(); } void CThreadPool::MoveToIdleList(CWorkerThread* busythread) { m_IdleMutex.Lock(); m_IdleList.push_back(busythread); m_AvailNum++; m_IdleMutex.Unlock(); m_BusyMutex.Lock(); vector<CWorkerThread*>::iterator pos; pos = find(m_BusyList.begin(),m_BusyList.end(),busythread); if(pos!=m_BusyList.end()) m_BusyList.erase(pos); m_BusyMutex.Unlock(); m_IdleCond.Signal(); m_MaxNumCond.Signal(); } //create num idle thread and put them to idlelist void CThreadPool::CreateIdleThread(int num) { for(int i=0;i<num;i++){ CWorkerThread* thr = new CWorkerThread(); thr->SetThreadPool(this); AppendToIdleList(thr); m_VarMutex.Lock(); m_AvailNum++; m_VarMutex.Unlock(); thr->Start(); //begin the thread,the thread wait for job } } void CThreadPool::DeleteIdleThread(int num) { printf("Enter into CThreadPool::DeleteIdleThread/n"); m_IdleMutex.Lock(); printf("Delete Num is %d/n",num); for(int i=0;i<num;i++){ CWorkerThread* thr; if(m_IdleList.size() > 0 ){ thr = (CWorkerThread*)m_IdleList.front(); printf("Get Idle thread %d/n",thr->GetThreadID()); }