linux posix 线程池_linux多线程--POSIX Threads Programming
linux多線程自己從接觸很久也有不少實踐,但總是覺得理解不夠深刻,不夠系統(tǒng)。借這篇文章試著再次系統(tǒng)學(xué)習(xí)一下linux多線程編程,理解編程的concept,細致看一下POSIX pthread API的實現(xiàn)。還是憑借強大的google search,找到幾篇不錯的文章和教程附在最后。我在這篇文章中的總結(jié)大多都是基于這些材料的學(xué)習(xí)和自己實踐經(jīng)驗的一點總結(jié)。
Thread基本知識
Process 地址空間
Thread附著在process內(nèi)部,先看一下process在CPU上是個什么樣的吧。啟動一個linux process,OS會開辟一塊內(nèi)存用來裝載code,保存data和process狀態(tài)。看一下進程的地址空間。
根據(jù)訪問權(quá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是這臺機器上所有processes共享的,每個進程的PCB存在這個空間中,一般應(yīng)用程序是沒有辦法直接訪問修改的,但是kernel 通過/proc 提供給應(yīng)用程序一個接口可以查看PCB的信息,部分內(nèi)容還可以修改,詳細可以看一下/proc。剩下的stack/heap/text/...都駐留在process user space,是屬于process私有空間。詳細的kernel如何管理進程memory還可以再開一篇。
Thread是什么?
process是個重型的運行實體,以process為單位切分任務(wù)和調(diào)度,os的開銷太大了。我們可以把process這個單位再切小些,thread的概念就誕生了。好,我們來看一下怎樣把這個單位切小的。簡單來講,thread共享大部分的process的內(nèi)容,只維護必需的一小部分作為私有內(nèi)容。
Thread自己維護的私有內(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中進程信息,用戶空間中的text/data/heap/...都是同一個process下所有Threads共享的。有了這些thread自己私有的信息,os就可以以thread為單位去調(diào)度了。因為它是輕量級的,所以相比process,thread一般具有更好的性能,更快的響應(yīng)速度。但是thread的穩(wěn)定性和編程復(fù)雜度要比process差些,要考慮的內(nèi)容比較多。
Threads通信和同步
正因為同一個process內(nèi)的threads間天然共享了大量的內(nèi)存,thread間的信息交互要比較高效,同時也增加了復(fù)雜度,總要處理好共享內(nèi)存間的互斥。當(dāng)然process間也可以共享內(nèi)存,比如通過進程父子關(guān)系,或者通過/dev/shm mmap特定物理內(nèi)存到進程空間內(nèi)或者其他。
線程間通信
所有的IPC(inter process communication)方法都適用于thread間的通信。比較全的IPC總結(jié),可以參考IPC。比較常用的我們會涉及到message queue,sharememory,semaphore,socket,signal等。semaphore是共享資源互斥的方法,其他都是冗余的方式進行通信。互斥是個比較復(fù)雜的話題,我們單開一節(jié)討論一下。
共享資源的互斥
為什么要保護共享資源做互斥訪問,這里不羅嗦了。通過對共享資源(臨界區(qū))加鎖可以實現(xiàn)互斥訪問,互斥鎖(mutex)也有多種類型。
simple blocking
一方拿到臨界區(qū)鎖后,其它人再來拿鎖都會掛起。
Recursive(遞歸型)
允許鎖的擁有者多次申請鎖而不被掛起,對遞歸調(diào)用有用。
Reader/Writer
允許多個reader同時share讀鎖,如果有reader在讀,writer申請鎖會block直到所有reader釋放。可以理解為一寫多讀,寫時互斥。這種鎖有寫?zhàn)I死的風(fēng)險。
其中POSIX的pthread庫支持recursive和reader/writer類型的鎖。
共享訪問帶來的風(fēng)險和挑戰(zhàn)
共享訪問中有寫操作,必然要考慮互斥。互斥有風(fēng)險,使用需謹慎。如果你最終不可避免的要使用互斥鎖,要關(guān)注互斥鎖的這些風(fēng)險。
deadlock(死鎖)
死鎖一般發(fā)生在雙方或者多方在申請兩個以上的互斥鎖,然后大家各拿了部分,互不相讓。開發(fā)者要盡量避免這種編程場景發(fā)生,如果真的需要可以編程要么同時獲得,要么一個都不要,做人要有骨氣!
race condition(競爭條件)
共享資源在沒有互斥機制保護時,由于線程調(diào)度的不確定性會導(dǎo)致共享的資源變化無序無規(guī)律,程序的輸出也就不確定了。共享資源無互斥保護,線程間競爭訪問,輸出無法保證。這要求開發(fā)者要特別小心識別出程序中的那些共享資源,加鎖保護。尤其是第三方的開源軟件,多線程調(diào)用時要注意是否是線程安全的。
priority reversion(優(yōu)先級反轉(zhuǎn))
優(yōu)先級反轉(zhuǎn)是個很有意思的問題,尤其是在嵌入式實時OS上,進程/線程的調(diào)度是搶占式的,高優(yōu)先級的任務(wù)ready時可以直接搶占CPU,這事再加上互斥就容易出問題了。比如三個任務(wù)H,M,L,優(yōu)先級遞減,同時H和L共享資源R。當(dāng)L先申請到互斥鎖訪問臨界區(qū)還沒釋放R的時候,H這時候申請R訪問導(dǎo)致自己掛起,這么巧M變ready了,OS調(diào)度讓M搶占了L的cpu。如果L一直得不到執(zhí)行并釋放R,這樣就造成了高優(yōu)先級的H得不到執(zhí)行,反而一些比H優(yōu)先級低的M們能得到CPU。這就是優(yōu)先級反轉(zhuǎn)。實時OS的高優(yōu)先級任務(wù)一般都是比較重要的任務(wù)需要馬上處理,得不到處理意味著可能要出大事。所以這個問題的影響還是挺大的,比較著名的例子就是火星探路者的故事,可以參考一下火星探路者故障分析。解決方法也有不少
盡量避免不同優(yōu)先級的任務(wù)共享資源,可以通過信息容易做任務(wù)間通信。
訪問臨界區(qū)時關(guān)閉中斷,保證臨界區(qū)的代碼執(zhí)行不被強占。嵌入式編程中常用。
優(yōu)先級繼承,當(dāng)有高優(yōu)先級任務(wù)想要訪問共享資源時,提高正在執(zhí)行的低優(yōu)先級任務(wù)的優(yōu)先級到高優(yōu)先級級別直至退出臨界區(qū)。上面的探路者修正程序使用了該方法。
隨機提高ready且持有鎖的任務(wù)優(yōu)先級,windows用了該方法。
Multi Threads應(yīng)用場景
寫了這么多,那到底什么時候可以應(yīng)用多線程來解決問題呢?根據(jù)經(jīng)驗,一般下面一些場景我們可以考慮使用多線程。
多核處理器,任務(wù)比較容易切分為并行處理的小塊。如果是計算密集型的,線程數(shù)量可以考慮跟core的數(shù)量相當(dāng)。
有delay比較多的IO操作,可以考慮將IO的操作分離給單獨的線程。
有人機交互和實時響應(yīng)等實時性要求較高任務(wù),可以考慮分離為優(yōu)先級較高的線程。
有大量實時要求不高的計算,可以考慮分離為優(yōu)先級較低的后臺任務(wù)。
Thread編程模型
實事求是,具體問題具體分析是放之四海而皆準的問題解決之道,所以沒有普適的編程模型。下面列舉3種應(yīng)用比較多的模型以供學(xué)習(xí)。
Thread Pool (Master/Worker)
通過線程池維護一組可用的線程,master作為主線程負責(zé)管理維護worker線程,同時負責(zé)對外接口和工作的分發(fā)。
Peer (Workcrew)
跟master/worker類似,只是master在啟動線程池后退化為普通一員,大家一起分擔(dān)任務(wù),沒有主從的星形拓撲結(jié)構(gòu)。
Pipeline
跟CPU的pipline技術(shù)類似,將一個工作流分成很多串行的部分,每一部分都由不同的線程負責(zé),大家各司其職,我做完我的工作就轉(zhuǎn)交給下一個線程,齊心協(xié)力最后完成整個工作。流水線如果拍的好可以很好的提高工作效率,但是這種模型風(fēng)險也比較大,一定要處理好工作的切分,和線程間的交互。
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可運行的CPU,也就是綁定CPU
pthread_join (threadid,status) # 阻塞等待threadid指定的thread完成
pthread_detach (threadid) # 線程創(chuàng)建默認是joinable,調(diào)用該函數(shù)設(shè)置線程的狀態(tài)為detached,則該線程運行結(jié)束后會自動釋放所有資源,別人再join等待它時候不會阻塞了。
pthread_attr_setdetachstate (attr,detachstate)
pthread_attr_getdetachstate (attr,detachstate)
pthread_self () # 返回自己所在的線程id
pthread_equal (thread1,thread2) # 比較兩個線程
大部分API見名思意比較簡單,詳細看一下pthread_create.
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
參數(shù)說明:
thread: 指針,所指向的內(nèi)存存儲新創(chuàng)建thread的屬性,返回給caller來標識該線程
attr: thread的配置參數(shù)集
start_routine: thread創(chuàng)建后執(zhí)行的處理函數(shù),thread的主體
arg: start_routine的入?yún)?/p>
功能說明:
創(chuàng)建thread API,成功后返回0. 創(chuàng)建的thread跟創(chuàng)建者是平行關(guān)系,沒有等級繼承關(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) # 動態(tài)生成一個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)用者會阻塞
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)用者阻塞時會自動釋放mutex,喚醒時會自動lock mutex。調(diào)用前確保lock mutex,調(diào)用后確保調(diào)用unlock mutex
pthread_cond_signal (condition) # 通知對方條件滿足,調(diào)用前確保lock mutex,調(diào)用后確保調(diào)用unlock mutex
pthread_cond_broadcast (condition)
條件變量是另外一種線程間同步的方式,其實是一種掛起和喚醒的通信方式。可以理解為定義一個條件變量定義了一個線程間的通信通道,wait這個變量一方其實是在等待有人在這個通道上發(fā)個信號來,如果沒有人發(fā)信號他就一直阻塞掛起。它需要跟mutex配合使用,直接通過一個例子感受一下。條件變量的存在就是讓wait的這一方睡起來直到有人通知它條件滿足可以起來干活了,否則沒有條件變量只用mutex做同步,這個wait的一方需要不斷的查詢是否條件滿足,低效浪費。
#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
上面提到的讀寫鎖。允許多個reader同時share讀鎖,如果有reader在讀,writer申請鎖會block直到所有reader釋放。
總結(jié)
以上是生活随笔為你收集整理的linux posix 线程池_linux多线程--POSIX Threads Programming的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux启动盘制作工具有哪些(linu
- 下一篇: soapui 证书_SoapUI入门之附