概述
多線程程式作為一種多任務、並發的工作方式,有以下的優點:
1) 提高應用程式響應。這對圖形介面的程式尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程式不會響應鍵盤、滑鼠、菜單的操作,而使用多線程技術,將耗時間長度的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。
2) 使多CPU系統更加有效。作業系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
3) 改善程式結構。一個既長又複雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程式會利於理解和修改。
Linux下最常用的是遵循POSIX標準的pthread線程庫。pthread的實現是通過系統調用clone()這一Linux特有的系統調用來實現。
其涉及的多線程開發的最基本概念主要包含三點:線程,互斥鎖,條件。
其中,線程操作又分線程的建立,退出,等待 3 種。
互斥鎖則包括 4 種操作,分別是建立,銷毀,加鎖和解鎖。
條件操作有 5 種操作:建立,銷毀,觸發,廣播和等待。
其他的一些線程擴充概念,如號誌等,都可以通過上面的三個基本元素的基本操作封裝出來。
表 1. 線程函數列表
對象 |
操作 |
Linux Pthread API |
Windows SDK 庫對應 API |
線程 |
建立 |
pthread_create |
CreateThread |
退出 |
pthread_exit |
ThreadExit |
等待 |
pthread_join |
WaitForSingleObject |
互斥鎖 |
建立 |
pthread_mutex_init |
CreateMutex |
銷毀 |
pthread_mutex_destroy |
CloseHandle |
加鎖 |
pthread_mutex_lock |
WaitForSingleObject |
解鎖 |
pthread_mutex_unlock |
ReleaseMutex |
條件 |
建立 |
pthread_cond_init |
CreateEvent |
銷毀 |
pthread_cond_destroy |
CloseHandle |
觸發 |
pthread_cond_signal |
SetEvent |
廣播 |
pthread_cond_broadcast |
SetEvent / ResetEvent |
等待 |
pthread_cond_wait / pthread_cond_timedwait |
SingleObjectAndWait |
1.線程建立與結束
1.1) pthread_t
線程的標識符類型,pthread_t在標頭檔/usr/include/bits/pthreadtypes.h中定義
typedef unsigned long int pthread_t;
1.2) pthread_create
thread_create用來建立一個線程,它的原型為:
extern int pthread_create __P ((pthread_t *__thread,
__const pthread_attr_t *__attr,
void *(*__start_routine) (void *),
void *__arg));
第一個參數為指向線程標識符的指標,
第二個參數用來設定線程屬性,設為空白指標,這樣將產生預設屬性的線程,
第三個參數是線程運行函數的起始地址,
最後一個參數是運行函數的參數,
當建立線程成功時,函數返回0,若不為0則說明建立線程失敗,常見的錯誤傳回碼為EAGAIN和EINVAL。前者表示系統限制建立新的線程,例如線程數目過多了;後者表示第二個參數代表的線程屬性值非法。建立線程成功後,新建立的線程則運行參數三和參數四確定的函數,原來的線程則繼續運行下一行代碼.
1.3) pthread_join
函數pthread_join用來等待一個線程的結束。函數原型為:
extern int pthread_join __P ((pthread_t __th,
void **__thread_return));
第一個參數為被等待的線程標識符,
第二個參數為一個使用者定義的指標,它可以用來儲存被等待線程的傳回值.
這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回.
1.4) pthread_exit
線程除了正常執行結束外,還可以通過函數pthread_exit來結束它,pthread_exit的函數原型為:
extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));
唯一的參數是函數的傳回碼,只要pthread_join中的第二個參數thread_return不是NULL,這個值將被傳遞給 thread_return.
最後要說明的是,一個線程不能被多個線程等待,否則第一個接收到訊號的線程成功返回,其餘調用pthread_join的線程則返回錯誤碼ESRCH.
2 修改線程屬性
屬性結構為pthread_attr_t,在標頭檔 /usr/include/pthread.h中定義。
初始化的函數為 pthread_attr_init,這個函數必須在pthread_create函數之前調用。
屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先順序。預設的屬性為非綁定、非分離、預設1M的堆棧、與父進程同樣層級的優先順序。
2.1)關於線程的綁定
線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為核心線程,它位於使用者層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。預設狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設定被綁定的輕進程的優先順序和調度級可以使得綁定的線程滿足諸如即時反應之類的要求。
設定線程綁定狀態的函數為 pthread_attr_setscope,它有兩個參數,第一個是指向屬性結構的指標,第二個是綁定類型,它有兩個取值: PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的).
下面的代碼即建立了一個綁定的線程。
pthread_attr_t attr;
pthread_t tid;
/*初始化屬性值,均設為預設值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
2.2)關於線程的狀態:分離態/非分離態
線程的分離狀態決定一個線程以什麼樣的方式來終止自己。在上面的例子中,我們採用了線程的預設屬性,即為非分離狀態,這種情況下,原有的線程等待建立的線程結束。只有當pthread_join()函數返回時,建立的線程才算終止,才能釋放自己佔用的系統資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。程式員應該根據自己的需要,選擇適當的分離狀態
設定線程分離狀態的函數為
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。
這裡要注意的一點是,如果設定一個線程為分離線程,而這個線程運行又非常快,它很可能在 pthread_create函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的線程裡調用 pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設定一段等待時間,是在多線程編程裡常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。
2.3) 關於線程的優先順序
線程的優先順序,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數 pthread_attr_setschedparam進行存放,一般說來,我們總是先取優先順序,對取得的值修改後再存放回去.
線程的優先順序取值範圍為-20~20,值越大,其優先順序越低,預設值為0,這個參數僅當調度策略為即時(即SCHED_RR或SCHED_FIFO)時才有效,
在運行時通過pthread_setschedparam()函數來改變.
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;
pthread_attr_init(&attr);
pthread_attr_getschedparam(&attr, param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr, param);
pthread_create(&tid, &attr, (void *)myfunction, myarg);