linux下多線程程式設計(c語言)
2.6核心開始使用nptl(native posix thread library)線程庫,這個線程庫有以下幾個目标: posix相容,都處理結果和應用,底啟動開銷,低連結開銷,與linux thread應用的二進制相容,軟硬體的可擴充能力,與c++內建等。
這裡的線程是指使用者空間的線程操作
一、線程相關操作
1.1 pthread_t
pthread_t 在頭檔案 /usr/include/i386-linux-gnu/bits/pthreadtypes.h中定義:
typedef unsigned long int pthread_t;
它是一個線程的辨別符(線程id)。
1.2 pthread_create
用來建立一個線程,它的原型為:
extern int pthread_create __p ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));
(1)、第一個參數為指向線程辨別符的指針(線程id),第二個參數用來設定線程屬性,第三個參數是線程運作函數的起始位址,最後一個參數是運作函數的參數。
(2)、若函數線程運作函數thread不需要參數,則最後一個參數應設為空指針。
(3)、第二個參數選擇設為空指針,則将生成預設屬性的線程。(可以對其屬性進行設定和修改)
(4)、當建立線程成功時,函數傳回0,若不為0則說明建立線程失敗。
常見的錯誤傳回代碼為eagain和einval。
eagain表示系統限制建立新的線程,例如線程數目過多了;
einval表示第二個參數代表的線程屬性值非法。
(5)建立線程成功後,新建立的線程則運作參數三和參數四确定的函數,原來的線程則繼續運作下一行代碼。
(6)attr: 線程屬性包括:優先級、初始棧大小,是否應該成為一個守護線程。
預設設定,null
tidp是要建立的線程,建立成功後tipd為先線程的id
void *(* func) (void *)是一個函數指針,該函數指針的類型為void* (*)(void *)
1.3 pthread_join 和 pthread_exit
線程退出的三種方式:
(1) 線上程建立以後,就開始運作相關的線程函數,在該線程函數運作完之後,該線程也就退出了。這是線程退出的一種方法: 運作完畢,自動退出;
(2) 調用pthread_exit函數主動退出;
(3) 程序終止函數exit函數,一旦結束了程序,那麼此程序中所有線程都将無條件終止。
注意點:在預設線程屬性下,如果一個程序有很多線程在同時運作,一個線程在退出以後,目前線程所占用的資源并不會随着線程的終止而得到釋放。因為所有處在一個程序中的線程共享資源。
線程中還有一個常用函數:pthread_join函數可以用于将目前線程挂起,,等待其他線程結束。實際上,這個函數是就是一個線程阻塞函數,調用它的函數将一直等待到被等待的線程結束為止。當函數傳回時,被等待線程的資源就被回收。
extern int pthread_join __p ((pthread_t __th, void **__thread_return));
th: 等待線程的辨別符
thread_return:使用者定義的指針,用來存儲被等待線程的傳回值(不為null時)
成功:0
void pthread_exit(void *retval)
retval:調用者線程的傳回值,可由其他函數如pthread_join來檢索擷取。
1.4 互斥鎖相關
互斥鎖用來保證一段時間内隻有一個線程在執行一段代碼。
必須被初始化為pthread_mutex_initializer(用于靜态配置設定的mutex,等價于 pthread_mutex_init(…, null))或者調用pthread_mutex_init。mutex也應該用pthread_mutex_destroy來銷毀。
(1) pthread_mutex_init
函 數pthread_mutex_init用來生成一個互斥鎖。null參數表明使用預設屬性。
(2) pthread_mutex_lock pthread_mutex_unlock pthread_delay_np
pthread_mutex_lock表示開始用互斥鎖上鎖,此後的代碼直至調用pthread_mutex_unlock為止,均被上鎖,即同一時間隻 能被一個線程調用執行。當一個線程執行到pthread_mutex_lock處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程式将等待到另一 個線程釋放此互斥鎖。
(3) sleep是為了讓線程睡眠一段時間,讓線程釋放互斥鎖,等待另一個線程使用此鎖。
1.5取消一個線程
1.5.1 int pthread_cancel(pthread_t thread);
thread:線程的辨別符 成功:0
1.5.2 在取消線形程請求的接收端,線程可以用pthread_setcancelstate設定自己的取消狀态,
int pthread_setcancelstate(int state,int *oldstate);(是否接受取消的請求)
state:可以是pthread_cancel_enable,這個值允許線程接收取消請求;還可以是pthread_cancel_disable,它的作用是螢幕它們。
線程以前的取消狀态可以用oldstate指針檢索出來。如果沒興趣可以傳一個null進去。
1.5.3 如果取消請求被接受了,線程會進入第二個控制層次----用pthread_setcanceltype設定取消類型。
int pthread_setcanceltype(int type,int *oldstate);
type:可以有兩種取值:
(1)一個是pthread_cancel_asynchoronous,接收到取消請求之後立刻采取行動;
(2)另一個是 pthread_cancel_deferred,在接收到取消請求之後、采取實際行動之前,先執行以下幾個函數之一:pthread_join、 pthread_cond_wait、pthread_cond_timewait、pthread_testcancel、sem_wait或 sigwait。
1.6 擷取線程自身的id
pthread_t pthread_self(void);
1.7線程清理處理程式
void pthread_clean_push(void (*rtn)(void *),void *arg)
void pthread_clean_pop(iny excute)
清理函數的調用順序是由pthread_clean_push函數安排的。
它在下列幾種情況下執行:
1.調用pthread_exit時
2.響應取消請求時
3.用非零execute參數調用pthread_clean_pop時
如果execute參數為0,清理函數将不被調用。無論何種情況,pthread_clean_pop都将删除上次pthread_clean_push建立的清理處理程式。
如果線程使用return從例程傳回,那麼pthread_clean_push建立的清理處理程式不會被執行。
1.8 讀寫鎖:reader-writer locks
1.8.1 多個線程可以同時獲得讀鎖(reader-writer lock in read mode),但是隻有一個線程能夠獲得寫鎖(reader-writer lock in write mode)
1.8.2 讀寫鎖有三種狀态
(1).一個或者多個線程獲得讀鎖,其他線程無法獲得寫鎖
(2)一個線程獲得寫鎖,其他線程無法獲得讀鎖
(3).沒有線程獲得此讀寫鎖
1.8.3類型為pthread_rwlock_t
1.8.4建立和關閉讀寫鎖
int pthread_rwlock_init( pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_rdlock:獲得讀鎖
pthread_rwlock_wrlock:獲得寫鎖
pthread_rwlock_unlock:釋放鎖,不管是讀鎖還是寫鎖都是調用此函數
注意具體 實作可能對同時獲得讀鎖的線程個數有限制,是以在調用 pthread_rwlock_rdlock的時候需要檢查錯誤值,而另外兩個pthread_rwlock_wrlock和 pthread_rwlock_unlock則一般不用檢查,如果我們代碼寫的正确的話。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
1.8.4 conditional variable:條件
a. 條件必須被mutex保護起來
b. 類型為:pthread_cond_t,必須被初始化為pthread_cond_initializer(用于靜态配置設定的條件,等價于pthread_cond_init(…, null))或者調用pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condxattr_t *restrict attr)
int pthread_cond_destroy(pthread_cond_t *cond);
c. pthread_cond_wait 函數用于等待條件發生(=true)。pthread_cond_timedwait類似,隻是當等待逾時的時候傳回一個錯誤值etimedout。逾時 的時間用timespec結構指定。此外,兩個函數都需要傳入一個mutex用于保護條件
int pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,
const struct timespec *restrict timeout);
d. timespec結構定義如下:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
注意timespec的時間是絕對時間而非相對時間,是以需要先調用gettimeofday函數獲得目前時間,再轉換成timespec結構,加上偏移量。
e. 有兩個函數用于通知線程條件被滿足(=true):
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
兩者的差別是前者會喚醒單個線程,而後者會喚醒多個線程。
1.9 (????????????????????????)
int pthread_detach(pthread_t tid);
線程或者是可彙合的 (joinable)或者是脫離的(detached)。當可彙合的線程終止時,其線程id和退出狀态将保留,直到另外一個線程調用 pthread_join。脫離的線程則像守護程序:當它終止時,所有的資源都釋放,我們不能等待它終止。如果一個線程需要知道另一個線程什麼時候終止, 最好保留第二個線程的可彙合性。pthread_detach函數将指定的線程變為脫離的。該函數通常被想脫離自己的線程調用,如:pthread_detach (pthread_self ( ));
1.10
線程安全:概念比較直覺。一般說來,一個函數被稱為線程安全的,當且僅當被多個并發線程反複調用時,它會一直産生正确的結果。
可重入:概念基本沒有比較正式的完整解釋,但是它比線程安全要求更嚴格。根據經驗,所謂“重入”,常見的情況是,程式執行到某個函數foo()時,收到信号,于是暫停目前正在執行的函數,轉到信号處理函數,而這個信号處理函數的執行過程中,又恰恰也會進入到剛剛執行的函數foo(),這樣便發生了所謂的重入。此時如果foo()能夠正确的運作,而且處理完成後,之前暫停的foo()也能夠正确運作,則說明它是可重入的。
線程安全的條件:
要 確定函數線程安全,主要需要考慮的是線程之間的共享變量。屬于同一程序的不同線程會共享程序記憶體空間中的全局區和堆,而私有的線程空間則主要包括棧和寄存 器。是以,對于同一程序的不同線程來說,每個線程的局部變量都是私有的,而全局變量、局部靜态變量、配置設定于堆的變量都是共享的。在對這些共享變量進行通路 時,如果要保證線程安全,則必須通過加鎖的方式。
可重入的判斷條件:
要確定函數可重入,需滿足一下幾個條件:
1、不在函數内部使用靜态或全局資料
2、不傳回靜态或全局資料,所有資料都由函數的調用者提供。
3、使用本地資料,或者通過制作全局資料的本地拷貝來保護全局資料。
4、不調用不可重入函數。
可重入與線程安全并不等同,一般說來,可重入的函數一定是線程安全的,但反過來不一定成立。
比如:strtok函數是既不可重入的,也不是線程安全的;加鎖的strtok不是可重入的,但線程安全;而strtok_r既是可重入的,也是線程安全的。