作者:CppExplore 網(wǎng)址:http://www.shnenglu.com/CppExplore/
廢話不多說,詳細(xì)介紹使用線程的優(yōu)點好處請參考baidu、google。
一、線程使用場景。使用線程的方式大致有兩種:
(1)流水線方式。根據(jù)業(yè)務(wù)特點,將一個流程的處理分割成多個線程,形成流水線的處理方式。產(chǎn)生的結(jié)果:延長單一流程的處理時間,提高系統(tǒng)整體的吞吐能力。
(2)線程池方式。針對處理時間比較長且沒有內(nèi)蘊狀態(tài)的線程,使用線程池方式分流消息,加快對線程消息的處理,避免其成為系統(tǒng)瓶頸。
線程使用的關(guān)鍵是 線程消息隊列、線程鎖、智能指針 的 使用。其中以線程消息隊列最為重要。
二、線程消息隊列描述。所謂線程消息隊列,就是一個普通的循環(huán)隊列(其它數(shù)據(jù)結(jié)構(gòu)也未嘗不可,具體內(nèi)容請參考數(shù)據(jù)結(jié)構(gòu)課本)加上“多生產(chǎn)者-單(多)消費者的PV操作”(詳細(xì)內(nèi)容請參考操作系統(tǒng)課本)。流水線方式中的線程是單消費者,線程池方式中的線程是多消費者。
為了后文更好的描述問題,作如下說明:
(1)假定循環(huán)隊列CircleQueue中,存放的消息指針類型是MyMSG *,入隊操作EnQueue,出隊操作DeQueue,判斷隊滿IsQueueFull,判斷隊空IsQueueEmpty。
(2)生產(chǎn)者消費者:生產(chǎn)者線程生產(chǎn)消息(MyMSG *),放在一個空緩沖區(qū)(CircleQueue)中,供消費者線程消費,生產(chǎn)者生產(chǎn)消息(EnQueue),如果緩沖區(qū)滿(IsQueueFull),則被阻塞,消費者消費消息(DeQueue),如果緩沖區(qū)空(IsQueueEmpty),則被阻塞。線程消息隊列就是生產(chǎn)者消費者問題中的緩沖區(qū),而它的生產(chǎn)者是不限定的,任何線程都可以作為生產(chǎn)者向其中進(jìn)行EnQueue操作,消費線程則可能是一個,也可能是多個。因此對循環(huán)隊列的任何操作都要加鎖,以保證線程安全。
PV操作和鎖機制的基礎(chǔ)都是信號量。下面列出posix標(biāo)準(zhǔn)中給出的有關(guān)信號量的操作:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t * sem);
int sem_trywait(sem_t * sem);
int sem_post(sem_t * sem);
int sem_getvalue(sem_t * sem, int * sval);
int sem_destroy(sem_t * sem);
各個函數(shù)的詳細(xì)用法,請在linux/unix上查看man。
三、線程消息隊列實現(xiàn)。基于以上討論,下面給出線程消息隊列的實現(xiàn)(僅為了說明問題,非標(biāo)準(zhǔn)可運行代碼)。
class ThreadQueue
{
CircleQueue * queue;
int nFull;
int nEmpty;
sem_t lock;
sem_t fullCond;
sem_t emptyCond
};
void createThreadQueue()
{
createCircleQueue(queue);
nFull=0;
nEmpty=0;
sem_ini(&lock,0,1);
sem_init(&fullCond,0,0);
sem_init(&emptyCond,0,0);
}
void putq(MyMSG * msg)
{
sem_wait(&lock);
if(IsQueueFull(queue))
{
nFull++;
sem_post(&lock);
sem_wait(&fullCond);
sem_wait(&lock);
nFull--;
}
EnQueue(queue,msg);
if(nEmpty>0)
{
sem_post(&emptyCond);
}
sem_post(&lock);
}
void getq(MyMSG * msg)
{
sem_wait(&lock);
if(IsQueueEmpty(queue))
{
nEmpty++;
sem_post(&lock);
sem_wait(&emptyCond);
sem_wait(&lock);
nEmpty--;
}
DeQueue(msg);
if(nFull>0)
{
sem_post(&fullCond);
}
sem_post(&lock);
}
void destroyThreadQueue()
{
destroyCircleQueue(queue);
sem_destroy(&lock);
sem_destroy(&fullCond);
sem_destroy(&emptyCond);
}

四、線程消息隊列使用說明。將線程和線程消息隊列封裝在一起,形成帶有消息隊列的線程,其它線程向該線程的消息隊列插入消息,本線程取消息處理,之后再向其它線程的消息隊列插入消息,如此形成流水線運行方式。線程的創(chuàng)建可以使用posix的pthread_create函數(shù),或者boost的boost::thread。具體使用請查看相關(guān)文檔。另ACE中的ACE_Task實現(xiàn)了帶有消息隊列的線程,可以直接使用。
五、線程鎖描述。線程鎖,應(yīng)該都很熟悉,通常的實現(xiàn)以mutex面目示人。假設(shè)實現(xiàn)后的操作有:加鎖lock,解鎖unlock。
所謂線程鎖就是同一時間只能有一個線程擁有的鎖。當(dāng)一個線程通過lock獲得線程鎖以后,在該線程持有該鎖的期間,其它進(jìn)行獲取鎖操作的線程只能阻塞在lock操作處,但該線程可以繼續(xù)對鎖進(jìn)行l(wèi)ock操作而不阻塞。
六、線程鎖實現(xiàn)
class mutex
{
sem_t lock;
sem_t used;
int nInOwner;
pthread_t owner;
};
mutex()
{
sem_init(&lock,0,1);
sem_init(&used,0,0);
owner=NULL;
nInOwner=0;
}
void lock()
{
pthread_t curThread=pthread_self();
sem_wait(&lock);
if(pthread_equal(owner,NULL))
{
owner=curThread;
}
else if(pthread_equal(curThread,owner)==0)
{
sem_post(&lock);
sem_wait(&used);
sem_wait(&lock);
owner=curThread;
}
nInOwner++;
sem_post(&lock);
}
void unlock()
{
sem_wait(&lock);
if(--nInOwner==0)
{
owner=NULL;
nInOwner=0;
sem_post(&used);
}
sem_post(&lock);
}
~mutex()
{
sem_destroy(&lock);
sem_destroy(&used);
}
七、線程鎖使用說明。系統(tǒng)設(shè)計中應(yīng)該盡量減少鎖的使用。但有的時候無法避免,這時就是mutex登場的時候了。mutex的實現(xiàn),linux下有pthread_mutex_t,ACE里有ACE_Thread_Mutex,boost里有boost::mutex。為了高效的操作可以進(jìn)一步實現(xiàn)出其它不同的鎖機制,比如常見的讀寫鎖,條件鎖,不再多說,有興趣可以自己去實現(xiàn),詳細(xì)可以參考操作系統(tǒng)課本。另linux/ACE/boost中均有實現(xiàn)。
lock和unlock要成對使用,但是很多情況下,一個函數(shù)有很多出口,再加上異常的情況,需要針對一個lock寫很多unlock,這樣不僅容易遺漏unlock,而且代碼也變得很丑陋。ACE中提供了Guard封裝mutex,使用起來比較方便,使用的時候不需要關(guān)心鎖的釋放,具體請看ACE。
也可以自己實現(xiàn)這種類Guard的功能。代碼如下:
class Guard
{
mutex *m_mutex;
};
Guard(mutex *lock):m_mutex(lock)
{
m_mutex->lock();
}
~Guard()
{
m_mutex->unlock();
}
使用的時候只需要在函數(shù)開始處寫std::auto_ptr<Guard> guard(new Guard(lock)) ;這屬于智能指針使用的一個小技巧。
八、使用智能指針的需求。在線程池方式中,為了去掉內(nèi)蘊狀態(tài),線程間不得不傳遞對象指針,這樣很難判斷指針的生命周期,難以找到釋放內(nèi)存空間的合適位置。智能指針完美解決了這個問題。boost中有boost::shared_ptr,ACE中有ACE_Refcounted_Auto_Ptr。本遍主要講述線程相關(guān),智能指針不再展開。
九、線程間消息傳遞框架。
(1)面向過程的消息傳遞。c語言常用方式。消息以結(jié)構(gòu)體的形式定義。
enum MsgType
{
CONCRETE_MSG1=1,
CONCRETE_MSG2=2
};
struct MyMsg
{
MsgType type;
union union_st
{
concreteMsg1 *msg1;
concreteMsg2 *msg2;
}msg;
};
concreteMsg1 和concreteMsg2 的詳細(xì)結(jié)構(gòu)不再列出。消息發(fā)送線程構(gòu)建正確的具體消息,指明正確的消息類型,進(jìn)一步構(gòu)建正確的MyMsg,發(fā)送到線程消息隊列。消息處理線程在消息隊列頭端循環(huán)getq,取出消息,根據(jù)消息類型調(diào)用相應(yīng)的方法處理。
(2)面向?qū)ο蟮南鬟f。線程消息隊列中存儲command模式中ICommand類型的指針。消息發(fā)送線程實例化具體的command,消息處理線程取出command執(zhí)行command的execute方法。
缺點是:command比較多的時候,會生成大量的類文件,代碼不夠緊湊。
優(yōu)點則是可以方便的增加command而不需要過多改動已有代碼。