多線程
使用多線程好處:
一、通過為每種事件類型的處理單獨分配線程,可以簡化處理異步事件的代碼,線程處理事件可以采用同步編程模式,啟閉異步編程模式簡單
二、方便的通信和數據交換
由於進程之間具有獨立的數據空間,多進程必須使用操作系統提供的復雜機制才能實現內存和文件描述符的共享,導致上下文切換的開銷比較大。而線程之間共享進程的所有資源,所以,多線程可以訪問相同的存儲空間和文件描述符。
三、對一些不具有互斥型的問題,可以將其分解為從而改善程序的吞吐量。對於進程,在完成多個任務時,實際上需要將任務串行化。對於多線程,相互獨立而且想不不依賴的任務可以交叉運行,只需要為每個任務分配一個線程即可。
四、交互的程序可以通過使用多線程實現響應時間的改善,多線程可以把程序中用於處理用戶輸入輸出的部分與其他部分分開。
線程包含了進程內執行環境必須的信息,包括進程ID,一組寄存器值,棧,調度優先級和策略,信號屏蔽字,errno變量以及線程私有數據。
線程ID使用pthread_t數據類型來表示,類似於一種結構體,可以使用 pthread_self 函數和 pthread_equal 函數來通過線程ID識別線程。
原型:
#include <pthread.h>
int pthread_eaual(pthread_t tid1,pthread_t tid2); //判斷兩個線程ID是否相等
相等返回非0,不相等返回0
#include <pthread.h>
pthread_t pthread_self(void); //獲取自身線程ID
返回調用線程的ID
線程創建
#include <pthread.h>
int pthread_creat(pthread_t *restrict tidp,const pthread_attr_t *attr,void*(*start_rtn)(void),void *restrict arg); //創建線程
成功返回0,否則返回錯誤編號
tidp 指向的內存單元被設置為新創建的現成的線程ID
attr 用於定制線程的線程屬性,設置為NULL時,則使用默認屬性
新創建的函數從strat_rtn函數的地址開始運行,該函數只有一個無類型指針參數arg.若要想向strat_rtn傳遞不知一個參數,可以將多個參數放在一個結構體中,然后把結構體的地址作為arg參數傳入。
線程終止
如果進程中的任一線程調用了exit,_Exit或_exit,則整個進程會終止。同樣,如果信號的默認動作是終止進程,那么,把信號發送到進程會終止整個進程。
單個進程的退出方式有三種:
1、線程只是從啟動的例程中退出,返回值是線程的終止碼;
2,、線程可以被統一進程中的其他線程取消;
3、線程調用pthread_exit;
#include<pthread.h>
void pthread_exit(void *rval_ptr);
線程通過調用pthread_exit函數終止執行,就如同進程在結束時調用exit函數一樣。這個函數的作用是,終止調用它的線程並返回一個指向某個對象的指針。
rval_ptr是一個無類型的指針,與傳遞給啟動例程的單個參數類似,可以通過進程中的其他線程調用pthread_join函數訪問這個指針。
#include<pthread.h>
int pthread_join(pthread_t thread,void **rval_ptr);
成功返回0;否則返回錯誤編號
pthread_join()函數,以阻塞的方式等待thread指定的線程調用pthread_exit、沖啟動例程中返回或者被取消。當函數返回時,被等待線程的資源被收回。
如果線程已經結束,那么該函數會立即返回。並且thread指定的線程必須是joinable的。
thread: 線程標識符,即線程ID,標識唯一線程。rval_ptr: 用戶定義的指針,用來存儲被等待線程的返回值。
如果線程是從啟動例程中返回,
rval_ptr將包含返回碼,如果線程被取消,由rval_ptr指向的內存單元被置為PTHREAD_CANCELED。
線程同步
線程同步機制包括互斥,讀寫鎖以及條件變量
互斥
可以把互斥變量之置為常量PTHREAD_MUTEX_INITIALIZER(針對靜態分配的互斥量),或調用pthread_mutex_init函數進行初始化。如果動態的分配互斥量(如調用malloc函數),那么在釋放內存前需要調用pthread_mutex_destory。
#include<pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destory(pthread_mutex_t mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,否則返回錯誤編號
如果線程不希望被堵塞,可以調用pthread_mutex_trylock嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量處於未鎖住狀態,則pthread_mutex_trylock將互斥量鎖住,不會出現阻塞並返回0,否則失敗並返回EBUSY。
初始化
#include<pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //初始化
int pthread_rwlock_destory(pthread_rwlock_t *restrict rwlock); //銷毀讀寫鎖,
成功返回0,錯誤返回錯誤編號;
鎖定讀寫鎖與解鎖
#include<pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t rwlock); //在讀模式下鎖定讀寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t rwlock); //在寫模式下鎖定讀寫鎖
int pthread_rwlock_unlock(pthread_rwlock_t rwlock); //解鎖
成功返回0,錯誤返回錯誤編號;
1、初始化讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); 與 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;作用相同
如果 attr 為 NULL,則使用缺省的讀寫鎖屬性,其作用與傳遞缺省讀寫鎖屬性對象的地址相同。初始化讀寫鎖之后,該鎖可以使用任意次數,而無需重新初始化。成功初始化之后,讀寫鎖的狀態會變為已初始化和未鎖定。如果調用pthread_rwlock_init() 來指定已初始化的讀寫鎖,則結果是不確定的。如果讀寫鎖在使用之前未初始化,則結果是不確定的。對於 Solaris 線程,請參見rwlock_init語法。
如果缺省的讀寫鎖屬性適用,則 PTHREAD_RWLOCK_INITIALIZER 宏可初始化以靜態方式分配的讀寫鎖,其作用與通過調用pthread_rwlock_init() 並將參數attr 指定為 NULL 進行動態初始化等效,區別在於不會執行錯誤檢查。
pthread_rwlock_init 返回值
如果成功,pthread_rwlock_init() 會返回零。否則,將返回用於指明錯誤的錯誤號。
如果 pthread_rwlock_init() 失敗,將不會初始化rwlock,並且rwlock 的內容是不確定的。
2、獲取讀寫鎖中的讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock );
如果寫入器未持有讀鎖,並且沒有任何寫入器基於該鎖阻塞,則調用線程會獲取讀鎖。如果寫入器未持有讀鎖,但有多個寫入器正在等待該鎖時,調用線程是否能獲取該鎖是不確定的。如果某個寫入器持有讀鎖,則調用線程無法獲取該鎖。如果調用線程未獲取讀鎖,則它將阻塞。調用線程必須獲取該鎖之后,才能從pthread_rwlock_rdlock() 返回。如果在進行調用時,調用線程持有rwlock 中的寫鎖,則結果是不確定的。
為避免寫入器資源匱乏,允許在多個實現中使寫入器的優先級高於讀取器。例如,Solaris 線程實現中寫入器的優先級高於讀取器。 請參見rw_rdlock 語法。
一個線程可以在 rwlock 中持有多個並發的讀鎖,該線程可以成功調用pthread_rwlock_rdlock()n 次。該線程必須調用pthread_rwlock_unlock()n 次才能執行匹配的解除鎖定操作。
如果針對未初始化的讀寫鎖調用 pthread_rwlock_rdlock(),則結果是不確定的。
線程信號處理程序可以處理傳送給等待讀寫鎖的線程的信號。從信號處理程序返回后,線程將繼續等待讀寫鎖以執行讀取,就好像線程未中斷一樣。
pthread_rwlock_rdlock 返回值
如果成功,pthread_rwlock_rdlock() 會返回零。否則,將返回用於指明錯誤的錯誤號。
3、讀取非阻塞讀寫鎖中的鎖
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
pthread_rwlock_tryrdlock 返回值
如果獲取了用於在 rwlock 所引用的讀寫鎖對象中執行讀取的鎖,則pthread_rwlock_tryrdlock() 將返回零。如果沒有獲取該鎖,則返回用於指明錯誤的錯誤號--EBUSY。
4、寫入讀寫鎖中的鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );
如果沒有其他讀取器線程或寫入器線程持有讀寫鎖 rwlock,則調用線程將獲取寫鎖。否則,調用線程將阻塞。調用線程必須獲取該鎖之后,才能從pthread_rwlock_wrlock() 調用返回。如果在進行調用時,調用線程持有讀寫鎖(讀鎖或寫鎖),則結果是不確定的。
為避免寫入器資源匱乏,允許在多個實現中使寫入器的優先級高於讀取器。(例如,Solaris 線程實現允許寫入器的優先級高於讀取器。請參見rw_wrlock 語法。)
如果針對未初始化的讀寫鎖調用 pthread_rwlock_wrlock(),則結果是不確定的。
線程信號處理程序可以處理傳送給等待讀寫鎖以執行寫入的線程的信號。從信號處理程序返回后,線程將繼續等待讀寫鎖以執行寫入,就好像線程未中斷一樣。
pthread_rwlock_wrlock 返回值
如果獲取了用於在 rwlock 所引用的讀寫鎖對象中執行寫入的鎖,則pthread_rwlock_rwlock() 將返回零。如果沒有獲取該鎖,則返回用於指明錯誤的錯誤號
5、寫入非阻塞讀寫鎖中的鎖
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
如果針對未初始化的讀寫鎖調用 pthread_rwlock_trywrlock(),則結果是不確定的。
線程信號處理程序可以處理傳送給等待讀寫鎖以執行寫入的線程的信號。從信號處理程序返回后,線程將繼續等待讀寫鎖以執行寫入,就好像線程未中斷一樣。
pthread_rwlock_trywrlock 返回值
如果獲取了用於在 rwlock 引用的讀寫鎖對象中執行寫入的鎖,則pthread_rwlock_trywrlock() 將返回零。否則,將返回用於指明錯誤的錯誤號--EBUSY。
6、解除鎖定讀寫鎖
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
如果調用線程未持有讀寫鎖 rwlock,則結果是不確定的。對於 Solaris 線程,請參見rw_unlock 語法。
如果通過調用 pthread_rwlock_unlock() 來釋放讀寫鎖對象中的讀鎖,並且其他讀鎖當前由該鎖對象持有,則該對象會保持讀取鎖定狀態。如果pthread_rwlock_unlock() 釋放了調用線程在該讀寫鎖對象中的最后一個讀鎖,則調用線程不再是該對象的屬主。如果pthread_rwlock_unlock() 釋放了該讀寫鎖對象的最后一個讀鎖,則該讀寫鎖對象將處於無屬主、解除鎖定狀態。
如果通過調用 pthread_rwlock_unlock() 釋放了該讀寫鎖對象的最后一個寫鎖,則該讀寫鎖對象將處於無屬主、解除鎖定狀態。
如果 pthread_rwlock_unlock() 解除鎖定該讀寫鎖對象,並且多個線程正在等待獲取該對象以執行寫入,則通過調度策略可確定獲取該對象以執行寫入的線程。如果多個線程正在等待獲取讀寫鎖對象以執行讀取,則通過調度策略可確定等待線程獲取該對象以執行寫入的順序。如果多個線程基於rwlock 中的讀鎖和寫鎖阻塞,則無法確定讀取器和寫入器誰先獲得該鎖。
如果針對未初始化的讀寫鎖調用 pthread_rwlock_unlock(),則結果是不確定的。
7、pthread_rwlock_destroy
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);與pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;相同
在再次調用 pthread_rwlock_init() 重新初始化該鎖之前,使用該鎖所產生的影響是不確定的。實現可能會導致pthread_rwlock_destroy() 將rwlock 所引用的對象設置為無效值。如果在任意線程持有 rwlock 時調用 pthread_rwlock_destroy(),則結果是不確定的。嘗試銷毀未初始化的讀寫鎖會產生不確定的行為。已銷毀的讀寫鎖對象可以使用pthread_rwlock_init() 來重新初始化。銷毀讀寫鎖對象之后,如果以其他方式引用該對象,則結果是不確定的。對於 Solaris 線程,請參見rwlock_destroy語法。
pthread_rwlock_destroy 返回值
如果成功,pthread_rwlock_destroy() 會返回零。否則,將返回用於指明錯誤的錯誤號--EINVAL。
條件變量
和互斥量一樣,可以把條件變量之置為常量PTHREAD_COND_INITIALIZER(針對靜態分配的條件變量),或調用pthread_cond_init函數進行初始化。在釋放內存前需要調用pthread_cond_destory函數對變量進行去除初始化。
#include<pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destory(pthread_cond_t cond);
成功返回0,否則返回錯誤編號
條件變量是與條件測試一起使用的,通常線程會對一個條件進行測試,如果條件不滿足調用條件等待函數就等待條件滿足。
#include<pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex,const struct timespec *rectrict timedout);
成功返回0,否則返回錯誤編號
cond是一個指向條件變量的指針,mutex是一個指向互斥量的指針,線程在調用前應該擁有這個互斥量。傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者吧鎖住的互斥量傳遞給函數,函數把調用線程防盜等待條件的線程列表上,然后互斥量解鎖,pthread_cond_wait返回時,互斥量再次被鎖住。
pthread_cond_timedwait與pthread_cond_wait工作方式相似,只是多了一個timeout。timeout是一個絕對值而不是相對值。其為需要等待的時間加上當前時間,即未來某一時刻。
timeout通過timespec結構指定:
struct timespec{
time_t tv_sec;
time_t tv_nsec;
};
可以使用gettimeofday獲取用timeval表示的當前時間,然后把等待時間轉化成timespec結構。
可以使用一下函數得到timeout值得絕對時間
void maketimeout(struct timespec *tsp,long minutes)
{
struct timeval now;
gettimeofday(&now); //獲取當前時間
tsp->tv_sec=now.tv_sec;
tsp->tv_nsec=now.tv_usec*1000; //usec轉換成nsec
tsp->tv_sec+=minutes*60;
}
pthread_cond_signal和pthread_cond_broadcast兩個函數用來通知線程條件已經滿足。
pthread_cond_signal在條件滿足后喚醒等待條件的某一個線程,而pthread_cond_broadcast將喚醒所有線程。
#include<pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
成功返回0,否則返回錯誤編號
注意:一定要在條件狀態改變以后再給線程發信號
