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

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