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