Unix多線程編程技術

posix pthreads庫提供了一系列的編寫多線程程序的函數主要包括
1. 創建和中止線程函數
2. 同步線程和對程序資源加鎖函數
3. 管理線程時序函數
一般地使用線程時序管理函數會複雜你的程序算法不僅如此在你移植你在單處
理機上的多線程程序到多處理機環境時也可能會帶來麻煩所以這裏不討論它
每一個線程都可以訪問到相同的全局變量和文件但每個線程也有它自己的堆棧和寄
存器
pthread_create函數
當一個程序被exec開始執行時會創建一個線程該線程稱作初始線程(initial
thread)或主線程(main thread) 其他的線程使用pthread_create創建
#include <pthread.h>
int pthread_create( pthread_t *tid, const pthread_attr_t *attr,
void *(*func)(void*), void* arg);
Returns: 0 if OK, 正數Exxx value on error.
一個進程中的每個線程都有一個線程ID來標識線程ID的數據類型爲pthread_t
(通常爲 unsigned int ) 如果創建新線程成功它的ID通過tid指針返回
每個線程有很多屬性調度優先級初始堆棧大小以daemon方式運行等等
我們可以通過pthread_attr_t變量來設定這些屬性如果attr爲一個空指針線程的屬性
爲缺省值通常情況下我們都使用缺省值
當創建一個線程的時候通常都會指定一個開始函數(start function) 線程在開
始執行時調用該函數然後線程顯式地(調用pthread_exit)或隱含地(執行函數start
function返回)終止開始函數地址爲func變量該函數調用時有一個指針參數arg 如果
開始函數需要多個參數必須構造一個結構把所有的參數作爲結構的成員然後把
結構指針的地址作爲唯一的參數傳遞給開始函數(start function)
注意變量func和arg 函數func有一個通用類型指針的變量(void *) 返回一個通
用類型的指針(void *) 這允許我們傳遞一個指針(可以指向任意類型)給線程線程返
回一個指針(指向任務類型)
通常情況下如果執行成功Pthread函數返回0 失敗時返回非0 和socket函數
以及大多數的系統調用不同這些函數出錯時返回1 和一個正的errno值指明失敗原
因Pthread返回一個正的錯誤號作爲返回值例如如果在調用pthread_create創建新
線程時因爲超過了系統線程數的限制而失敗則pthread_create返回EAGAIN
Pthread函數不使用errno變量成功返回0 失敗返回一個正數的約定是考慮周到的因
爲所有定義在<sys/errno.h>的出錯參數Exxx的值都是正數並且0是沒有使用的
pthread_join函數
調用pthread_join可以等待指定的線程終止把線程與進程相對比的話
pthread_create相當於fork pthread_join相當於waitpid
#include <pthread.h>
1 2002-08-20
int pthread_join( pthread_t tid, void **status );
Returns:0 if OK positive Exxx 值 on error
我們必須指定等待線程的tid 非常不幸我們沒有方法等待任意一個線程的終
止(類似於waitpid中的進程ID參數的值爲1) 我們將在圖23.13中繼續討論這個問題
如果status的指針爲非NONE 線程的返回值(是一個指向某對象的指針)保存在
被status所指向的本地地址
當一個線程調用了pthread_join後此線程進入睡眠狀態直到pthread_join指定的線
程中止後才繼續執行pthread_join函數主要的功能是來回收指定線程的系統資源如
果你在程序中忘記了調用pthread_join 而有用pthread_create創建了多過線程程序運
行時可能會因爲資源不足而出問題這就想你多次調用了malloc函數而忘記了調用
free函數一樣
有時候你可能不關心一個線程的存在狀態 而只是要求那個線程爲你完成一項工
作這時候你可以用pthread_detach函數被pthread_detach指定後的線程在退出時會自
動釋放佔據的系統資源
pthread_self 函數
在一個進程中所有的線程都有一個ID來標識線程ID在調用pthread_create函數
時返回在pthread_join函數中也使用了線程ID 一個線程可以使用pthread_self來取得
其本身的線程ID
#include <pthread.h>
pthread_t pthread_self( void );
Returns: thread ID of calling thread.
pthread_self類似於UNIX進程的getpid
pthread_detach 函數
線程分爲可連接的(joinable)和不可連接的(detached) 缺省狀態下線程是可連接
的當一個可連接的線程終止時它的線程ID和終止狀態將一直保存到有另外一個線
程調用pthread_join 一個不可連接的線程類似於幽靈(daemon)進程當它終止時將
釋放所有的資源並且無法等待它的終止如果一個線程需要知道另外一個線程何時
終止最好讓其爲可連接的(joinable)
pthread_detach函數將指定的進程變爲不可連接的(detached)
#include <pthread.h>
int pthread_detach( pthread_t tid );
Returns: 0 if OK 正的Exxx值 on error
此函數最常用的情況是使某線程本身不可連接如
pthread_detach( pthread_self());
pthread_exit 函數
線程終止的一種方法是調用pthread_exit函數
2 2002-08-20
#include <pthread.h>
void pthread_exit( void *status);
如果線程不是detached 它的線程ID和終止狀態將一直保存到進程中有其他的線
程調用函數pthread_join
status指針一定不能指向線程的局部變量因爲在線程終止時該變量也會隨之消

另外還有兩種情況會導致線程終止
(1)線程開始執行時調用的函數(pthread_create的第三個變量)返回既然開始函數
必須返回一個void指針因此函數的返回值就是線程的終止狀態
(2)進程的main函數返回或任何一個線程調用exit時進程及其所有的線程終止
pthread_mutex_lock函數
當線程要訪問(讀或寫)共享數據的時候必須對共享數據加鎖例
int i; //i是全局數據
pthread_mutex_t iLock;
...
pthread_mutex_lock(&iLock);
//to do something to i
pthread_mutex_unlock(&iLock);
...
當線程調用pthread_mutex_lock把共享數據鎖住後其他線程要使用此數據時就會被暫
時掛起直到線程調用pthread_mutex_unlock解鎖
需要注意的時一個線程不應該一直鎖住共享數據即調用pthread_mutex_lock後應該盡
可能快調用pthread_mutex_unlock釋放共享區以便其他線程可以利用一般做法時
當要讀取一個共享數據時先調用pthread_mutex_lock加鎖數據把共享數據拷貝到一
個臨時本地變量中去立即調用pthread_mutex_unlock解鎖
當 要 寫 一 個 共 享 數 據 時 先把要寫的數據準備在本地臨時變量中然後調用
phtread_mutex_lock加鎖共享數據把計算好了的數據寫入共享數據區然後立即調用
pthread_mutex_unlock解鎖
pthread_mutex_unlock函數
解鎖函數
pthread_mutex_init函數
初始化一個鎖變量
pthread_mutex_destroy函數
釋放一個鎖變量
pthread_mutex_t mutex;
pthread_mutex_destory(&mutex);
pthread_cond_init函數
初始化條件變量
pthread_cont_t cond;
pthread_cond_init(&cond);
pthread_cond_wait函數
3 2002-08-20
調用此函數會導致一個線程等待指定的條件變量被signaled或broadcasted 看下
面的語句
pthread_mutex_lock(&mutex); /* lock mutex */
while (!predicate) { /* check predicate */
pthread_cond_wait(&condvar,&mutex); /* go to sleep - recheck
pred on awakening */
}
pthread_mutex_unlock(&mutex); /* unlock mutex */
當predicate等於0時程序將調用pthread_cond_wait pthread_cond_wait將做下面的兩件

Ÿ unlocks the mutex
Ÿ puts the thread to sleep
線程這時將進入睡眠狀態爲了喚醒它在另外一個線程中可以用
pthread_mutex_lock(&mutex); /* lock the mutex */
predicate=1; /* set the predicate */
pthread_cond_broadcast(&condvar); /* wake everyone up */
pthread_mutex_unlock(&mutex); /* unlock the mutex */
pthread_cond_broadcast函數將喚醒所以的在等待條件變量condvar的線程
使用概要
1. 條件變量一個線程調用pthread_cond_wait後將進入睡眠狀態直到另外一個線程調
用pthread_cond_broadcast來喚醒條件變量用來完成這個行爲假如我們只想喚醒
單個進程可以用pthread_cond_signal函數
2. predicate 變量這個變量來決定是否要調用pthead_cond_wait函數也就是說決
定線程是否要進入睡眠狀態使用這個變量的原因是因爲喚醒線程只知道要喚醒
那些進入睡眠狀態的線材而它並不知道有多少線程進入了睡眠狀態假如沒有
predicate變量一個後來的線程可能錯過另一個線程已經發出的broadcast而進入睡
眠狀態這種情況可能不是你想要的
3. mutex 用互斥保證了線程安全存取predicate變量由於睡眠線程要讀取predicate
喚醒線程要寫變量predicate 所以必須對predicate使用互斥機制
爲什麼要使用一個while循環呢這是因爲一次只會喚醒一個睡眠的線程假如第
一個被喚醒的線程退出了循環並且又把predicate改爲了0 則其他的進程就不會被喚
醒這就是while帶來的好處
爲什麼pthread_cond_wait需要知道mutex變量呢我們來看一下下面把mutex分離出
來會產生什麼後果
pthread_mutex_lock(&mutex); /* lock mutex */
while (!predicate) { /* check predicate */
pthread_mutex_unlock(&mutex); /* unlock mutex */
pthread_cond_wait(&condvar); /* go to sleep - recheck
pred on awakening */
pthread_mutex_lock(&mutex); /* lock mutex */
}
pthread_mutex_unlock(&mutex); /* unlock mutex */
這段程序會產生很大的問題假如線程進入了while循環執行了
pthread_mutex_unlock函數這時被切換到另外一個線程而那個線程又請求了
mutex和設置了predicate變量這就導致了broadcast先於上面現成的pthread_cond_wait 執
行也就是說這個本來應該進入睡眠的線程卻沒有進入所以mutex和條件變量不應
4 2002-08-20
該分開操作pthread_cond_wait處理mutex和條件變量unix的內核會保證線程不會丟
失broadcast
pthread_cond_broadcast函數
這個喚醒那些等待條件變量的線程
sched_yield函數
通知線程調度程序調用另外一個線程運行
當一個線程調用sched_yield後將強迫自己放棄處理機直到它又處於線程列表
的頭部位置時才運行線程調用sched_yield函數通知線程調度程序調度一個級別和自
己相等或級別大於自己的線程如果沒有和自己級別相等或大於的線程則線程繼續
執行
線程安全
我們知道所有的全局數據在線程中被存取的時候是要用互斥機制來保護的而
當我們在線程中調用了系統的庫函數時情況又會是什麼樣呢由於你調用的系統函
數可能會使用到全局數據而它有沒有提供保護機制問題就出來了
一個例子是malloc函數malloc函數從全局的共享數據區分配一塊數據空間因
此加入同時有多個線程調用malloc 混亂的就有可能產生哦當然你可能會考慮用
lock/unlock機制來保證malloc不出這個問題但這是沒有用的爲什麼
假設每個線程調用了printf函數
printf("i=%d s=%s\n",anint,astring);
printf函數是有能暗中調用的malloc函數來分配緩衝區來打印astring字符串的而printf可
能根本不知道你的malloc調用要用到互斥機制保護所以假如有多個的線程來調用
printf和malloc 錯誤就可能產生
上面的例子說明了庫函數可能產生的問題但不幸的是沒有一般的方法來解
決這個問題POSIX threads規定了C語言函數庫是要thread-safe的而你如果使用其他
的沒有說明thread-safe的庫就必須手工來實現線程安全
5 2002-08-20
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章