linux posix 线程池_linux多线程--POSIX Threads Programming
linux多線程自己從接觸很久也有不少實(shí)踐,但總是覺得理解不夠深刻,不夠系統(tǒng)。借這篇文章試著再次系統(tǒng)學(xué)習(xí)一下linux多線程編程,理解編程的concept,細(xì)致看一下POSIX pthread API的實(shí)現(xiàn)。還是憑借強(qiáng)大的google search,找到幾篇不錯(cuò)的文章和教程附在最后。我在這篇文章中的總結(jié)大多都是基于這些材料的學(xué)習(xí)和自己實(shí)踐經(jīng)驗(yàn)的一點(diǎn)總結(jié)。
Thread基本知識(shí)
Process 地址空間
Thread附著在process內(nèi)部,先看一下process在CPU上是個(gè)什么樣的吧。啟動(dòng)一個(gè)linux process,OS會(huì)開辟一塊內(nèi)存用來(lái)裝載code,保存data和process狀態(tài)。看一下進(jìn)程的地址空間。
根據(jù)訪問(wèn)權(quán)限,進(jìn)程地址空間分為user space和kernel space。32bit系統(tǒng)中高1G為kernel space,低3G為user space,具體劃分為:
Process control block(從高1G kernel space中分配)
stack
memory mapping segment
heap
bss and data
text
1G的kernel space是這臺(tái)機(jī)器上所有processes共享的,每個(gè)進(jìn)程的PCB存在這個(gè)空間中,一般應(yīng)用程序是沒有辦法直接訪問(wèn)修改的,但是kernel 通過(guò)/proc 提供給應(yīng)用程序一個(gè)接口可以查看PCB的信息,部分內(nèi)容還可以修改,詳細(xì)可以看一下/proc。剩下的stack/heap/text/...都駐留在process user space,是屬于process私有空間。詳細(xì)的kernel如何管理進(jìn)程memory還可以再開一篇。
Thread是什么?
process是個(gè)重型的運(yùn)行實(shí)體,以process為單位切分任務(wù)和調(diào)度,os的開銷太大了。我們可以把process這個(gè)單位再切小些,thread的概念就誕生了。好,我們來(lái)看一下怎樣把這個(gè)單位切小的。簡(jiǎn)單來(lái)講,thread共享大部分的process的內(nèi)容,只維護(hù)必需的一小部分作為私有內(nèi)容。
Thread自己維護(hù)的私有內(nèi)容
Kernel space
Stack pointer
Registers
Scheduling properties (such as policy or priority)
Set of pending and blocked signals
Thread specific data.
User space
stack
其他諸如PCB中進(jìn)程信息,用戶空間中的text/data/heap/...都是同一個(gè)process下所有Threads共享的。有了這些thread自己私有的信息,os就可以以thread為單位去調(diào)度了。因?yàn)樗禽p量級(jí)的,所以相比process,thread一般具有更好的性能,更快的響應(yīng)速度。但是thread的穩(wěn)定性和編程復(fù)雜度要比process差些,要考慮的內(nèi)容比較多。
Threads通信和同步
正因?yàn)橥粋€(gè)process內(nèi)的threads間天然共享了大量的內(nèi)存,thread間的信息交互要比較高效,同時(shí)也增加了復(fù)雜度,總要處理好共享內(nèi)存間的互斥。當(dāng)然process間也可以共享內(nèi)存,比如通過(guò)進(jìn)程父子關(guān)系,或者通過(guò)/dev/shm mmap特定物理內(nèi)存到進(jìn)程空間內(nèi)或者其他。
線程間通信
所有的IPC(inter process communication)方法都適用于thread間的通信。比較全的IPC總結(jié),可以參考IPC。比較常用的我們會(huì)涉及到message queue,sharememory,semaphore,socket,signal等。semaphore是共享資源互斥的方法,其他都是冗余的方式進(jìn)行通信。互斥是個(gè)比較復(fù)雜的話題,我們單開一節(jié)討論一下。
共享資源的互斥
為什么要保護(hù)共享資源做互斥訪問(wèn),這里不羅嗦了。通過(guò)對(duì)共享資源(臨界區(qū))加鎖可以實(shí)現(xiàn)互斥訪問(wèn),互斥鎖(mutex)也有多種類型。
simple blocking
一方拿到臨界區(qū)鎖后,其它人再來(lái)拿鎖都會(huì)掛起。
Recursive(遞歸型)
允許鎖的擁有者多次申請(qǐng)鎖而不被掛起,對(duì)遞歸調(diào)用有用。
Reader/Writer
允許多個(gè)reader同時(shí)share讀鎖,如果有reader在讀,writer申請(qǐng)鎖會(huì)block直到所有reader釋放。可以理解為一寫多讀,寫時(shí)互斥。這種鎖有寫?zhàn)I死的風(fēng)險(xiǎn)。
其中POSIX的pthread庫(kù)支持recursive和reader/writer類型的鎖。
共享訪問(wèn)帶來(lái)的風(fēng)險(xiǎn)和挑戰(zhàn)
共享訪問(wèn)中有寫操作,必然要考慮互斥。互斥有風(fēng)險(xiǎn),使用需謹(jǐn)慎。如果你最終不可避免的要使用互斥鎖,要關(guān)注互斥鎖的這些風(fēng)險(xiǎn)。
deadlock(死鎖)
死鎖一般發(fā)生在雙方或者多方在申請(qǐng)兩個(gè)以上的互斥鎖,然后大家各拿了部分,互不相讓。開發(fā)者要盡量避免這種編程場(chǎng)景發(fā)生,如果真的需要可以編程要么同時(shí)獲得,要么一個(gè)都不要,做人要有骨氣!
race condition(競(jìng)爭(zhēng)條件)
共享資源在沒有互斥機(jī)制保護(hù)時(shí),由于線程調(diào)度的不確定性會(huì)導(dǎo)致共享的資源變化無(wú)序無(wú)規(guī)律,程序的輸出也就不確定了。共享資源無(wú)互斥保護(hù),線程間競(jìng)爭(zhēng)訪問(wèn),輸出無(wú)法保證。這要求開發(fā)者要特別小心識(shí)別出程序中的那些共享資源,加鎖保護(hù)。尤其是第三方的開源軟件,多線程調(diào)用時(shí)要注意是否是線程安全的。
priority reversion(優(yōu)先級(jí)反轉(zhuǎn))
優(yōu)先級(jí)反轉(zhuǎn)是個(gè)很有意思的問(wèn)題,尤其是在嵌入式實(shí)時(shí)OS上,進(jìn)程/線程的調(diào)度是搶占式的,高優(yōu)先級(jí)的任務(wù)ready時(shí)可以直接搶占CPU,這事再加上互斥就容易出問(wèn)題了。比如三個(gè)任務(wù)H,M,L,優(yōu)先級(jí)遞減,同時(shí)H和L共享資源R。當(dāng)L先申請(qǐng)到互斥鎖訪問(wèn)臨界區(qū)還沒釋放R的時(shí)候,H這時(shí)候申請(qǐng)R訪問(wèn)導(dǎo)致自己掛起,這么巧M變r(jià)eady了,OS調(diào)度讓M搶占了L的cpu。如果L一直得不到執(zhí)行并釋放R,這樣就造成了高優(yōu)先級(jí)的H得不到執(zhí)行,反而一些比H優(yōu)先級(jí)低的M們能得到CPU。這就是優(yōu)先級(jí)反轉(zhuǎn)。實(shí)時(shí)OS的高優(yōu)先級(jí)任務(wù)一般都是比較重要的任務(wù)需要馬上處理,得不到處理意味著可能要出大事。所以這個(gè)問(wèn)題的影響還是挺大的,比較著名的例子就是火星探路者的故事,可以參考一下火星探路者故障分析。解決方法也有不少
盡量避免不同優(yōu)先級(jí)的任務(wù)共享資源,可以通過(guò)信息容易做任務(wù)間通信。
訪問(wèn)臨界區(qū)時(shí)關(guān)閉中斷,保證臨界區(qū)的代碼執(zhí)行不被強(qiáng)占。嵌入式編程中常用。
優(yōu)先級(jí)繼承,當(dāng)有高優(yōu)先級(jí)任務(wù)想要訪問(wèn)共享資源時(shí),提高正在執(zhí)行的低優(yōu)先級(jí)任務(wù)的優(yōu)先級(jí)到高優(yōu)先級(jí)級(jí)別直至退出臨界區(qū)。上面的探路者修正程序使用了該方法。
隨機(jī)提高ready且持有鎖的任務(wù)優(yōu)先級(jí),windows用了該方法。
Multi Threads應(yīng)用場(chǎng)景
寫了這么多,那到底什么時(shí)候可以應(yīng)用多線程來(lái)解決問(wèn)題呢?根據(jù)經(jīng)驗(yàn),一般下面一些場(chǎng)景我們可以考慮使用多線程。
多核處理器,任務(wù)比較容易切分為并行處理的小塊。如果是計(jì)算密集型的,線程數(shù)量可以考慮跟core的數(shù)量相當(dāng)。
有delay比較多的IO操作,可以考慮將IO的操作分離給單獨(dú)的線程。
有人機(jī)交互和實(shí)時(shí)響應(yīng)等實(shí)時(shí)性要求較高任務(wù),可以考慮分離為優(yōu)先級(jí)較高的線程。
有大量實(shí)時(shí)要求不高的計(jì)算,可以考慮分離為優(yōu)先級(jí)較低的后臺(tái)任務(wù)。
Thread編程模型
實(shí)事求是,具體問(wèn)題具體分析是放之四海而皆準(zhǔn)的問(wèn)題解決之道,所以沒有普適的編程模型。下面列舉3種應(yīng)用比較多的模型以供學(xué)習(xí)。
Thread Pool (Master/Worker)
通過(guò)線程池維護(hù)一組可用的線程,master作為主線程負(fù)責(zé)管理維護(hù)worker線程,同時(shí)負(fù)責(zé)對(duì)外接口和工作的分發(fā)。
Peer (Workcrew)
跟master/worker類似,只是master在啟動(dòng)線程池后退化為普通一員,大家一起分擔(dān)任務(wù),沒有主從的星形拓?fù)浣Y(jié)構(gòu)。
Pipeline
跟CPU的pipline技術(shù)類似,將一個(gè)工作流分成很多串行的部分,每一部分都由不同的線程負(fù)責(zé),大家各司其職,我做完我的工作就轉(zhuǎn)交給下一個(gè)線程,齊心協(xié)力最后完成整個(gè)工作。流水線如果拍的好可以很好的提高工作效率,但是這種模型風(fēng)險(xiǎn)也比較大,一定要處理好工作的切分,和線程間的交互。
POSIX API詳解
Thread management
pthread_create (thread,attr,start_routine,arg) #創(chuàng)建thread
pthread_exit (status) # thread退出
pthread_cancel (thread) # 退出指定的thread
pthread_attr_init (attr) #初始化thread屬性
pthread_attr_destroy(attr)
pthread_setaffinity_np or sched_setaffinity # 設(shè)置thread可運(yùn)行的CPU,也就是綁定CPU
pthread_join (threadid,status) # 阻塞等待threadid指定的thread完成
pthread_detach (threadid) # 線程創(chuàng)建默認(rèn)是joinable,調(diào)用該函數(shù)設(shè)置線程的狀態(tài)為detached,則該線程運(yùn)行結(jié)束后會(huì)自動(dòng)釋放所有資源,別人再join等待它時(shí)候不會(huì)阻塞了。
pthread_attr_setdetachstate (attr,detachstate)
pthread_attr_getdetachstate (attr,detachstate)
pthread_self () # 返回自己所在的線程id
pthread_equal (thread1,thread2) # 比較兩個(gè)線程
大部分API見名思意比較簡(jiǎn)單,詳細(xì)看一下pthread_create.
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
參數(shù)說(shuō)明:
thread: 指針,所指向的內(nèi)存存儲(chǔ)新創(chuàng)建thread的屬性,返回給caller來(lái)標(biāo)識(shí)該線程
attr: thread的配置參數(shù)集
start_routine: thread創(chuàng)建后執(zhí)行的處理函數(shù),thread的主體
arg: start_routine的入?yún)?/p>
功能說(shuō)明:
創(chuàng)建thread API,成功后返回0. 創(chuàng)建的thread跟創(chuàng)建者是平行關(guān)系,沒有等級(jí)繼承關(guān)系。
thread有以下屬性
Detached or joinable state
Scheduling inheritance
Scheduling policy
Scheduling parameters
Scheduling contention scope
Stack size
Stack address
Stack guard (overflow) size
Mutexes
pthread_mutex_init (mutex,attr) # 動(dòng)態(tài)生成一個(gè)mutex變量
pthread_mutex_destroy (mutex) # 釋放mutex變量
pthread_mutexattr_init (attr) # 設(shè)置mutex屬性
pthread_mutexattr_destroy (attr)
pthread_mutex_lock (mutex) # lock操作,如果mutex已經(jīng)lock調(diào)用者會(huì)阻塞
pthread_mutex_trylock (mutex) # 嘗試lock,非阻塞調(diào)用
pthread_mutex_unlock (mutex) # unlock操作
Condition variables
pthread_cond_init (condition,attr)
pthread_cond_destroy (condition)
pthread_condattr_init (attr)
pthread_condattr_destroy (attr)
pthread_cond_wait (condition,mutex) # 調(diào)用者阻塞直到condition條件成立,注意調(diào)用者阻塞時(shí)會(huì)自動(dòng)釋放mutex,喚醒時(shí)會(huì)自動(dòng)lock mutex。調(diào)用前確保lock mutex,調(diào)用后確保調(diào)用unlock mutex
pthread_cond_signal (condition) # 通知對(duì)方條件滿足,調(diào)用前確保lock mutex,調(diào)用后確保調(diào)用unlock mutex
pthread_cond_broadcast (condition)
條件變量是另外一種線程間同步的方式,其實(shí)是一種掛起和喚醒的通信方式。可以理解為定義一個(gè)條件變量定義了一個(gè)線程間的通信通道,wait這個(gè)變量一方其實(shí)是在等待有人在這個(gè)通道上發(fā)個(gè)信號(hào)來(lái),如果沒有人發(fā)信號(hào)他就一直阻塞掛起。它需要跟mutex配合使用,直接通過(guò)一個(gè)例子感受一下。條件變量的存在就是讓wait的這一方睡起來(lái)直到有人通知它條件滿足可以起來(lái)干活了,否則沒有條件變量只用mutex做同步,這個(gè)wait的一方需要不斷的查詢是否條件滿足,低效浪費(fèi)。
#include
#include
#include
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
int count = 0;
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
void *inc_count(void *t)
{
int i;
long my_id = (long)t;
for (i=0; i < TCOUNT; i++) {
pthread_mutex_lock(&count_mutex);
count++;
/*
Check the value of count and signal waiting thread when condition is reached. Note that this occurs while mutex is locked.
*/
if (count == COUNT_LIMIT) {
printf("inc_count(): thread %ld, count = %d Threshold reached. ",
my_id, count);
pthread_cond_signal(&count_threshold_cv);
printf("Just sent signal.\n");
}
printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",
my_id, count);
pthread_mutex_unlock(&count_mutex);
/* Do some work so threads can alternate on mutex lock */
sleep(1);
}
pthread_exit(NULL);
}
void *watch_count(void *t)
{
long my_id = (long)t;
printf("Starting watch_count(): thread %ld\n", my_id);
/*
Lock mutex and wait for signal. Note that the pthread_cond_wait routine
will automatically and atomically unlock mutex while it waits.
Also, note that if COUNT_LIMIT is reached before this routine is run by
the waiting thread, the loop will be skipped to prevent pthread_cond_wait
from never returning.
*/
pthread_mutex_lock(&count_mutex);
while (count < COUNT_LIMIT) {
printf("watch_count(): thread %ld Count= %d. Going into wait...\n", my_id,count);
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %ld Condition signal received. Count= %d\n", my_id,count);
printf("watch_count(): thread %ld Updating the value of count...\n", my_id,count);
count += 125;
printf("watch_count(): thread %ld count now = %d.\n", my_id, count);
}
printf("watch_count(): thread %ld Unlocking mutex.\n", my_id);
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
int i, rc;
long t1=1, t2=2, t3=3;
pthread_t threads[3];
pthread_attr_t attr;
/* Initialize mutex and condition variable objects */
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init (&count_threshold_cv, NULL);
/* For portability, explicitly create threads in a joinable state */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&threads[0], &attr, watch_count, (void *)t1);
pthread_create(&threads[1], &attr, inc_count, (void *)t2);
pthread_create(&threads[2], &attr, inc_count, (void *)t3);
/* Wait for all threads to complete */
for (i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf ("Main(): Waited and joined with %d threads. Final value of count = %d. Done.\n",
NUM_THREADS, count);
/* Clean up and exit */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit (NULL);
}
Synchronization
pthread_rwlock_destroy
pthread_rwlock_init
pthread_rwlock_rdlock
pthread_rwlock_timedrdlock
pthread_rwlock_timedwrlock
pthread_rwlock_tryrdlock
pthread_rwlock_trywrlock
pthread_rwlock_unlock
pthread_rwlock_wrlock
pthread_rwlockattr_destroy
pthread_rwlockattr_getpshared
pthread_rwlockattr_init
pthread_rwlockattr_setpshared
上面提到的讀寫鎖。允許多個(gè)reader同時(shí)share讀鎖,如果有reader在讀,writer申請(qǐng)鎖會(huì)block直到所有reader釋放。
總結(jié)
以上是生活随笔為你收集整理的linux posix 线程池_linux多线程--POSIX Threads Programming的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux启动盘制作工具有哪些(linu
- 下一篇: soapui 证书_SoapUI入门之附