一.什麼是線程
在一個程序裏的多個執行路線就叫做線程。更準確的定義是:線程是“一個進程內部的一個控制序列”。
典型的unix進程可以看成只有一個控制線程:一個進程在同一時刻只做一件事情。有了多個控制線程以後,在程序設計時可以把進程設計成在同一時刻能夠做不止一件事,每個線程處理各只獨立的任務。
二.線程的優點
(1) 通過爲每種事件類型的處理分配單獨的線程,能夠簡化處理異步時間的代碼。
(2) 多個線程可以自動共享相同的存儲地址空間和文件描述符。
(3) 有些問題可以通過將其分解從而改善整個程序的吞吐量。
(4) 交互的程序可以通過使用多線程實現相應時間的改善,多線程可以把程序中處理用戶輸入輸出的部分與其它部分分開。
三.線程的缺點
線程也有不足之處。編寫多線程程序需要更全面更深入的思考。在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的。調試一個多線程程序也比調試一個單線程程序困難得多。
四.線程的結構
線程包含了表示進程內執行環境必需的信息,其中包括進程中標識線程的線程ID,一組寄存器值、棧、調度優先級和策略、信號屏蔽子,errno變量以及線程私有數據。進程的所有信息對該進程的所有線程都是共享的,包括可執行的程序文本,程序的全局內存和堆內存、棧以及文件描述符。
五.線程標識
就像每個進程有一個進程ID一樣,每個線程也有一個線程ID,進程ID在整個系統中是唯一的,但線程不同,線程ID只在它所屬的進程環境中有效。線程ID用pthread_t數據類型來表示,實現的時候可以用一個結構來代表pthread_t數據類型,所以可以移植的操作系統不能把它作爲整數處理。因此必須使用函數來對來對兩個線程ID進行比較。
1.
名稱:: |
pthread_equal |
功能: |
比較兩個線程ID |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_equal(pthread_t tid1,pthread_t tid2); |
參數: |
tid1 進程1id tid2 進程2id |
返回值: |
若相等返回非0值,否則返回0 |
2.
名稱:: |
pthread_self |
功能: |
獲取自身線程的id |
頭文件: |
#include <pthread.h> |
函數原形: |
pthread_t pthread_self(void); |
參數: |
無 |
返回值: |
調用線程的線程id |
六.線程的創建
3.
名稱:: |
pthread_create |
功能: |
創建線程 |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_create(pthread_t *restrict tidp,const pthread _attr_t *restrict attr,void *(*start_rtn)(void),void *restrict arg); |
參數: |
|
返回值: |
若成功返回則返回0,否則返回錯誤編號 |
當pthread_creat成功返回時, tidp指向的內存單元被設置爲新創建線程的線程ID。attr參數用於定製各種不同的線程屬性。可以把它設置爲NULL,創建默認的線程屬性。
新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針參數arg,如果需要向start_rtn函數傳遞的參數不止一個,那麼需要把這些參數放到一個結構中,然後把這個結構的地址作爲arg參數傳入。
#include <pthread.h>
void printids(const char *s) { printf(“%s pid:%u tid:%u /n“, getpid(),pthread_self()); }
void *thr_fn(void *arg) { printf (“new thread: “); }
int main() { int err; pthread_t tid; err=pthread_create(&tid,NULL,thr_fn,NULL); if(err=0) printf(“can’t create thread:%s/n”,strerror(err)); printids(“main thread: “); sleep(1); exit(0); } |
關於進程的編譯我們都要加上參數 –lpthread 否則提示找不到函數的錯誤。
具體編譯方法是 cc –lpthread –o gettid gettid.c
運行結果爲
main thread: pid 14954 tid 134529024
new thread: pid 14954 tid 134530048
七..線程的終止
線程是依進程而存在的,當進程終止時,線程也就終止了。當然也有在不終止整個進程的情況下停止它的控制流。
(1)線程只是從啓動例程中返回,返回值是線程的退出碼。
(2)縣城可以被同一進程中的其他線程取消。
(3)線程調用pthread_exit.
4.
名稱:: |
pthread_exit |
功能: |
終止一個線程 |
頭文件: |
#include <pthread.h> |
函數原形: |
void pthread_exit(void *rval_ptr); |
參數: |
|
返回值: |
無 |
rval_prt是一個無類型指針,與傳給啓動例程的單個參數類似。進程中的其他線程可以調用pthread_join函數訪問到這個指針。
5.
名稱:: |
pthread_join |
功能: |
獲得進程的終止狀態 |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_join(pthread_t thread,void **rval_ptr); |
參數: |
|
返回值: |
若成功返回0,否則返回錯誤編號。 |
當一個線程通過調用pthread_exit退出或者簡單地從啓動歷程中返回時,進程中的其他線程可以通過調用pthread_join函數獲得進程的退出狀態。調用pthread_join進程將一直阻塞,直到指定的線程調用pthread_exit,從啓動例程中或者被取消。
如果線程只是從它的啓動歷程返回,rval_ptr將包含返回碼。
#include <pthread.h> #include <string.h>
void *thr_fn1(void *arg) { printf(“thread 1 returning/n”); return((void *)1); }
void *thr_fn2(void *arg) { printf(“thread 2 exiting/n”); return((void *)2); }
int main() { pthread_t tid1,tid2; void *tret;
pthread_create(&tid1,NULL,thr_fn1,NULL); pthread_create(&tid2,NULL,thr_fn2,NULL); pthread_join(tid1,&tret); printf(“thread 1 exit code %d/n”,(int)tret); pthread_join(tid2,&tret); printf(“thread 2 exit code %d/n”,(int)tret); exit(0); } |
運行結果是:
thread 1 returning thread 2 exiting thread 1 exit code 1 thread 2 exit code 2 |
6.
名稱:: |
pthread_detach |
功能: |
使線程進入分離狀態。 |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_detach(pthread_t tid); |
參數: |
|
返回值: |
若成功則返回0,否則返回錯誤編號。 |
在默認情況下,線程的終止狀態會保存到對該線程調用pthread_join,如果線程已經處於分離狀態,線程的底層存儲資源可以在線程終止時立即被收回。當線程被分離時,並不能用pthread_join函數等待它的終止狀態。對分離狀態的線程進行pthread_join的調用會產生失敗,返回EINVAL.pthread_detach調用可以用於使線程進入分離狀態。
7.
名稱:: |
pthread_cancel |
功能: |
取消同一進程中的其他線程 |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_cancel(pthread_t tid); |
參數: |
tid 線程id |
返回值: |
若成功返回0,否則返回錯誤編號。 |
在默認的情況下,pthread_cancel函數會使由tid標識的線程的行爲表現爲如同調用了參數爲PTHEAD_CANCELED的pthread_exit函數,但是,線程可以選擇忽略取消方式和控制取消方式。pthread_cancel並不等待線程終止,它僅僅提出請求。
8.
名稱:: |
pthread_cancel_push/ pthread_cancel_push_pop |
功能: |
線程清理處理程序 |
頭文件: |
#include <pthread.h> |
函數原形: |
void pthread_cancel_push(void (*rtn)(void *),void *arg); void pthread_cancel_pop(int execute); |
參數: |
rtn 處理程序入口地址 arg 傳遞給處理函數的參數 |
返回值: |
無 |
線程可以安排它退出時需要調用的函數,這樣的函數稱爲線程清理處理程序,線程可以建立多個清理處理程序。處理程序記錄在棧中,也就是說它們的執行順序與它們註冊時的順序相反。
要注意的是如果線程是通過從他的啓動例程中返回而終止的,它的處理程序就不會調用。還要注意清理處理程序是按照與它們安裝時相反的順序調用的。
#include <pthread.h> #include <stdio.h>
void cleanup(void *arg) { printf(“cleanup: %s/n”,(char *)arg); }
void *thr_fn(void *arg) /*線程入口地址*/ { printf(“thread start/n”); pthread_cleanup_push(cleanup,”thread first handler”);/*設置第一個線程處理程序*/ pthread_cleanup_push(cleanup,”thread second handler”); /*設置第二個線程處理程序*/ printf(“thread push complete/n”); pthread_cleanup_pop(0); /*取消第一個線程處理程序*/ pthread_cleanup_pop(0); /*取消第二個線程處理程序*/ }
int main() { pthread_t tid; void *tret;
pthread_creat(&tid,NULL,thr_fn,(void *)1); /*創建一個線程*/ pthread_join(tid,&tret); /*獲得線程終止狀態*/ ptinrf(“thread exit code %d/n”,(int)tret); } |
八、一次性初始化
有時候我們需要對一些posix變量只進行一次初始化,如線程鍵(我下面會講到)。如果我們進行多次初始化程序就會出現錯誤。
在傳統的順序編程中,一次性初始化經常通過使用布爾變量來管理。控制變量被靜態初始化爲0,而任何依賴於初始化的代碼都能測試該變量。如果變量值仍然爲0,則它能實行初始化,然後將變量置爲1。以後檢查的代碼將跳過初始化。
但是在多線程程序設計中,事情就變的複雜的多。如果多個線程併發地執行初始化序列代碼,2個線程可能發現控制變量爲0,並且都實行初始話,而該過程本該僅僅執行一次。初始化的狀態必須由互斥量保護。
如果我們需要對一個posix變量靜態的初始化,可使用的方法是用一個互斥量對該變量的初始話進行控制。但有時候我們需要對該變量進行動態初始化,pthread_once就會方便的多。
9.
名稱:: |
pthread_once |
功能: |
一次性初始化 |
頭文件: |
#include <pthread.h> |
函數原形: |
pthread_once_t once_control=PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control,void(*init_routine)(void)); |
參數: |
once_control 控制變量 init_routine 初始化函數 |
返回值: |
若成功返回0,若失敗返回錯誤編號。 |
類型爲pthread_once_t的變量是一個控制變量。控制變量必須使用PTHREAD_ONCE_INIT宏靜態地初始化。
pthread_once函數首先檢查控制變量,判斷是否已經完成初始化,如果完成就簡單地返回;否則,pthread_once調用初始化函數,並且記錄下初始化被完成。如果在一個線程初始時,另外的線程調用pthread_once,則調用線程等待,直到那個現成完成初始話返回。
下面就是該函數的程序例子:
#include <pthread.h>
pthread_once_t once=PTHREAD_ONCE_INIT; pthread_mutex_t mutex;) /*互斥量,我們後面會講到*/
void once_init_routine(void) /*一次初始化函數*/ { int status; status=pthread_mutex_init(&mutex,NULL);/*初始化互斥量*/ if(status==0) printf(“Init success!,My id is %u”,pthread_self()); }
void *child_thread(void *arg) { printf(“I’m child ,My id is %u”,pthread_self()); pthread_once(&once,once_init_routine); /*子線程調用一次性初始化函數*/ }
int main(int argc,char *argv[ ]) { pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL);/*創建子線程*/ printf(“I’m father,my id is %u”,pthread_self()); pthread_once(&once_block,once_init_routine);/*父線程調用一次性初始化函數*/ pthread_join(child_thread_id,NULL); } |
程序運行結果如下:
./once
I’m father,My id is 3086874304
Init success!,My id is 3086874304
I’m child, My id is 3086871472
從上面的結果可以看到當主函數初始化成功後,子函數初始化失敗。
九、線程的私有數據
在進程內的所有線程共享相同的地址空間,任何聲明爲靜態或外部的變量,或在進程堆聲明的變量,都可以被進程所有的線程讀寫。那怎樣才能使線程序擁有自己的私有數據呢。
posix提供了一種方法,創建線程鍵。
10.
名稱:: |
pthread_key_create |
功能: |
建立線程私有數據鍵 |
頭文件: |
#include <pthread.h> |
函數原形: |
int pthread_key_create(pthread_key *key,void(*destructor)(void *)); |
參數: |
key 私有數據鍵 destructor 清理函數 |
返回值: |
若成功返回0,若失敗返回錯誤編號。 |
第一個參數爲指向一個鍵值的指針,第二個參數指明瞭一個destructor函數(清理函數),如果這個參數不爲空,那麼當每個線程結束時,系統將調用這個函數來釋放綁定在這個鍵上的內存塊。這個函數常和函數pthread_once一起使用,爲了讓這個鍵只被創建一次。函數pthread_once聲明一個初始化函數,第一次調用pthread_once時它執行這個函數,以後的調用將被它忽略。
下面是程序例子:
#include <pthread.h>
pthread_key_t tsd_key; pthread_once_t key_once=PTHREAD_ONCE_INIT;
void once_routine(void) { int status;
status=pthread_key_create(&tsd_key,NULL);/*初始化線程私有數據鍵*/ if(status=0) printf(“Key create success! My id is %u/n”,pthread_self()); }
void *child_thread(void *arg) { printf(“I’m child,My id is %u/n”,pthread_self()); pthread_once(&key_once,once_routine);/* 調用一次性初始化函數*/ }
int main(int argc,char *argv[ ]) { pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL); printf(“I’m father,my id is%u/n”,pthread_self()); pthread_once(&key_once,once_routine); } |
程序運行結果如下:
I’m father,My id is 3086231232
Key create success! My id is 3086231232
I’m child,My id is 2086228400