一,線程的基本概念
1,線程的定義:
線程也被稱為輕量進程(LWP)電腦科學術語,指運行中的程式的調度單位。
線程是進程中的實體,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程不擁有系統資源,只有運行必須的一些資料結構;它與父進程的其它線程共用該進程所擁有的全部資源。線程可以建立和撤消線程,從而實現程式的並發執行。一般,線程具有就緒、阻塞和運行三種基本狀態。
在多中央處理器的系統裡,不同線程可以同時在不同的中央處理器上運行,甚至當它們屬於同一個進程時也是如此。大多數支援多處理器的作業系統都提供編程介面來讓進程可以控制自己的線程與各處理器之間的關聯度。
2,線程與進程的對比
1>定義
進程是具有一定獨立功能的程式關於某個資料集合上的一次運行活動,進程是系統進行資源分派和調度的一個獨立單位。
線程比進程的粒度更細,線程是進程執行流中的分支,擁有進程一部分資源。
2>關係
一個線程可以建立和撤銷另一個線程;同一個進程中的多個線程之間可以並發執行。
相對進程而言,線程是一個更加接近於執行體的概念,它可以與同進程中的其他線程共用資料,但擁有自己的棧空間,擁有獨立的執行序列。
3>區別:
a.地址空間和其它資源:進程間相互獨立,同一進程的各線程間共用。某進程內的線程在其它進程不可見。
b.通訊:處理序間通訊IPC,線程間可以直接讀寫進程資料區段(如全域變數)來進行通訊——需要進程同步和互斥手段的輔助,以保證資料的一致性。
c.調度和切換:線程環境切換比進程環境切換要快得多。
d.在多線程OS中,進程不是一個可執行檔實體。
4>優缺點
線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。同時,線程適合於在SMP機器上運行,而進程則可以跨機器遷移。
3,多線程
main函數和訊號處理函數是同一個進程地址空間中的多個控制流程程,多線程也是如此,但是比訊號處理函數更加靈活,訊號處理函數的控制流程程只是在訊號遞達時產生,在處理完訊號之後就結束,而多線程的控制流程程可以長期並存,作業系統會在各線程之間調度和切換,就像在 多個進程之間調度和切換一樣。
同一進程的多個線程之間共用以下進程資源和環境:
1. 檔案描述符表
2. 每種訊號的處理方式(SIG_IGN、SIG_DFL或者自訂的訊號處理函數)
3. 當前工作目錄
4. 使用者id和組id
同一進程的多個線程之間的私人資源:
1. 線程id(是一個正整數,僅在當前進程內有效,用來標識線程)
2. 上下文,包括各種寄存器的值、程式計數器和棧指標
3. 棧空間
4. errno變數
5. 訊號屏蔽字
6. 調度優先順序
我將要介紹的線程庫函數是由POSIX標準定義的,稱為POSIX thread或者pthread。在Linux上線程函數位於libpthread共用庫中,因此在編譯時間要加上-lpthread選項。 二,線程式控制制
1,線程建立
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
傳回值:成功返回0,錯誤返回錯誤號碼
第一個參數thread:線程id(輸出型參數)
第二個參數attr:線程屬性,一般設定為NULL(表示線程屬性取預設值)
第三個參數start_routine:函數指標,指向新線程即將執行的代碼
第四個參數arg:這個指標按什麼類型解釋由調用者自己定義—->NULL
在一個線程中調用pthread_create()建立新的線程後,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指標start_routine決定。
代碼舉例:
由上例可知在Linux上,pthread_t類型是一個地址值,屬於同一進程的多個線程調用getpid()可以得到相同的進程號,而調用pthread_self()得到的線程號各不相同。
如果任意一個線程調用了exit或_exit,則整個進程的所有線程都終止,由於從main函數return也相當於調用exit(下面解釋)。
運行結果:
2,終止線程
1>如果需要只終止某個線程而不終止整個進程,可以有三種方法:
1. 從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit。
2. 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
3. 線程可以調用pthread_exit終止自己。
2>終止線程或執行流:
#include <pthread.h>void pthread_exit(void *retval);
retval是void *類型,和線程函數傳回值的用法一樣,其它線程可以調用pthread_join(稍後介紹)獲得這個指標。
需要注意,pthread_exit或者return返回的指標所指向的記憶體單元必須是全域的或者是用malloc分配的,不能線上程函數的棧上分配,因為當其它線程得到這個返回指標時線程函數已經退出了。
3>取消線程
#include <pthread.h>int pthread_cancel(pthread_t thread);
線程是允許被取消的,退出結果為-1,線程自己取消自己(不推薦)退出結果為0。
3,線程等待
1>為什麼要等待線程。
main函數執行的線程稱為主線程,多線程的執行順序由線程調度決定
主線程必須回收其他線程,否則就會產生類似殭屍進程的狀況(記憶體流失)。
結論:線程必須被等待
2>擷取當前線程的線程tid:pthread_self
#include <pthread.h> pthread_t pthread_self(void);
(僅僅在當前進程內部有效,作為線程的唯一識別碼)
3>線程等待函數:
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);
傳回值:成功返回0,失敗返回錯誤碼。
參數thread:線程號,即要等待線程的tid
參數retval:要等待線程的退出碼(輸出型參數)
調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
1. 如果thread線程通過return返回,value_ptr所指向的單元裡存放的是thread線程函數的傳回值。
2. 如果thread線程被別的線程調用pthread_cancel異常掉,value_ptr所指向的單元裡存放的是常數PTHREAD_CANCELED。
3. 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。
4>多線程的進行執行時,只要有一個線程出錯,整個進程就會掛掉(作業系統會向其發訊號回收資源,其他線程都跟著退出)。線程運行時,線程只能正常的運行完,退出碼錶明了其運行狀態
線程等待只有一種方式:阻塞式等待
4,綜合舉例
1 #include<stdio.h> 2 #include<pthread.h> 3 #include<unistd.h> 4 5 void* thread1(void* val1) 6 { 7 printf("thread1 is returning\n"); 8 printf("%s:pid is %d,tid is %u\n",(char*)val1,getpid(),pthread_self()); 9 return (void*)0;//線程終止方式1,用return返回 10 } 11 void* thread2(void* val2) 12 { 13 printf("thread2 exiting\n"); 14 printf("%s:pid is %d,tid is %u\n",(char*)val2,getpid(),pthread_self()); 15 pthread_exit((void*)2);//線程終止方式2,用pthread_exit退出 16 } 17 void* thread3(void* val3) 18 { 19 printf("%s:pid is %d,tid is %u\n",(char*)val3,getpid(),pthread_self()); 20 while(1) 21 { 22 printf("thread3 is running,waiting for be canceled\n");//線程終止方式3,被其他線程c ancel 23 sleep(1); 24 } 25 } 26 int main() 27 { 28 pthread_t tid1; 29 pthread_t tid2; 30 pthread_t tid3; 31 void* ret; 32 33 //thread1 return 34 pthread_create(&tid1,NULL,thread1,"thread1");//線程1建立 35 pthread_join(tid1,&ret);//wait thread1 36 printf("thread1 return,return code is %d\n",(int)ret); 37 38 //thread2 exit 39 pthread_create(&tid2,NULL,thread2,"thread2");//線程2建立 40 pthread_join(tid2,&ret);//wait thread2 41 printf("thread2 exit,exit code is %d\n",(int)ret); 42 43 //thread3 cancel 44 pthread_create(&tid3,NULL,thread3,"thread3");//線程3建立 45 sleep(3); 46 pthread_cancel(tid3);//線程終止方式3,被其他線程用thread_cancel取消 47 pthread_join(tid3,&ret);//wait thread3 48 printf("thread3 cancel,cancel code is %d\n",(int)ret); 49 50 printf("main thread run:pid is %d,tid is %u\n",getpid(),pthread_self()); 51 52 return 0; 53 }
運行結果:
細心一點的朋友們就會發現,為什麼3個線程的pid和tid都是相同的呢。原因就是pid實際上是主線程的進程號,同為1514,那麼既然是不同的線程,又因為線程號是確定一個線程的標識,為什麼3個線程號仍然相同呢。
其實不然,回顧代碼我們是驗證3種線程終止的方式,而且我們在每個線程終止後就對其進行了等待,調用過線程等待函數之後主線程就會回收其資源,其中當然也包括線程id,當線程1回收之後,我們緊接著建立了線程2,系統會優先選擇剛剛回收的1號線程id來分配給線程2,當線程2終止等待之後,此線程號再次被回收利用,進而分配給線程3,這就是為什麼我們看到不同的3個線程擁有同一個tid了。
如果你對上述問題還有質疑,請看下面的代碼:
1 #include<stdio.h> 2 #include<pthread.h> 3 #include<unistd.h> 4 5 void* thread1(void* val1) 6 { 7 printf("thread1 is returning\n"); 8 printf("%s:pid is %d,tid is %u\n",(char*)val1,getpid(),pthread_self()); 9 return (void*)0;//線程終止方式1,用return返回 10 } 11 void* thread2(void* val2) 12 { 13 printf("thread2 exiting\n"); 14 printf("%s:pid is %d,tid is %u\n",(char*)val2,getpid(),pthread_self()); 15 pthread_exit((void*)2);//線程終止方式2,用pthread_exit退出 16 } 17 void* thread3(void* val3) 18 { 19 printf("%s:pid is %d,tid is %u\n",(char*)val3,getpid(),pthread_self()); 20 while(1) 21 { 22 printf("thread3 is running,waiting for be canceled\n");//線程終止方式3,被其他線程c ancel 23 sleep(1); 24 } 25 } 26 int main() 27 { 28 pthread_t tid1; 29 pthread_t tid2; 30 pthread_t tid3; 31 void* ret; 32 33 //thread1 return 34 pthread_create(&tid1,NULL,thread1,"thread1");//線程1建立 35 // pthread_join(tid1,&ret);//wait thread1 36 printf("thread1 return,return code is %d\n",(int)ret); 37 38 //thread2 exit 39 pthread_create(&tid2,NULL,thread2,"thread2");//線程2建立 40 // pthread_join(tid2,&ret);//wait thread2 41 printf("thread2 exit,exit code is %d\n",(int)ret); 42 43 //thread3 cancel 44 pthread_create(&tid3,NULL,thread3,"thread3");//線程3建立 45 sleep(3); 46 pthread_cancel(tid3);//線程終止方式3,被其他線程用thread_cancel取消 47 // pthread_join(tid3,&ret);//wait thread3 48 printf("thread3 cancel,cancel code is %d\n",(int)ret); 49 50 printf("main thread run:pid is %d,tid is %u\n",getpid(),pthread_self()); 51 52 pthread_join(tid1,NULL); 53 pthread_join(tid2,NULL); 54 pthread_join(tid3,NULL); 55 return 0; 56 }
調整代碼:將程式中間對線程的等待注釋掉,在程式運行即將結束時對3個線程進行等待,在此期間3個子線程處於僵死狀態。
運行結果:
5,線程屬性
線程的兩種屬性:可結合性和可分離性。
在任何一個時間點上,線程是可結合的(joinable)或者是分離的(detached)。一個可結合的線程能夠被其他線程收回其資源和殺死。在被其他線程回收之前,它的儲存空間資源(例如棧)是不釋放的。相反,一個分離的線程是不能被其他線程回收或殺死的,它的存
儲器 資源在它終止時由系統自動釋放。
預設情況下,線程被建立成可結合的。
為了避免儲存空間泄漏,每個可結合線程都應該要麼被顯示地回收,即調用pthread_join;要麼通過調用pthread_detach函數被分離。
由於調用pthread_join後,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。改善方法:
1>可以在子線程中加入代碼:
pthread_detach(pthread_self())
2>父線程調用:
pthread_detach(thread_id)(非阻塞,可立即返回)
這將該子線程的狀態設定為分離的(detached),detached),如一來,該線程運行結束後會自動釋放所有資源。
線程可以主動分離,也可以被分離。
一個進程設定為分離線程,則主線程不需要對其等待,待其運行完系統會自動回收。
線程分離代碼:
1 /************************************** 2 *檔案說明:thread_detach.c 3 *作者:段曉雪 4 *建立時間:2017年05月22日 星期一 20時53分18秒 5 *開發環境:Kali Linux/g++ v6.3.0 6 ****************************************/ 7 #include<stdio.h> 8 #include<unistd.h> 9 #include<pthread.h> 10 #include<string.h> 11 12 void* thread4(void* val4) 13 { 14 printf("%s:thread4 child detach,thread4 tid is %d\n",(char*)val4,pthread_self());//線程 4主動分離 15 pthread_detach(pthread_self()); 16 return (void*)0;//線程終止 17 } 18 void* thread5(void* val5) 19 { 20 printf("%s:thread5 father detach,thread5 tid is %d\n",(char*)val5,pthread_self());//線> 程5被分離 21 return (void*)0;//線程終止 22 } 23 24 int main() 25 { 26 pthread_t tid4; 27 int ret = pthread_create(&tid4,NULL,thread4,"thread4 is running");//線程建立 28 if(ret != 0) 29 printf("creat thread4 error:errno is %d,error info is %s\n",ret,strerror(ret)); 30 //wait---線程等待 31 sleep(1); 32 int tmp = 0; 33 tmp = pthread_join(tid4,NULL); 34 if(0 == tmp) 35 printf("thread4 wait success\n"); 36 else 37 printf("thread4 wait failure\n"); 38 39 pthread_t tid5; 40 ret = pthread_create(&tid5,NULL,thread5,"thread5 is running");//線程建立 41 if(ret != 0) 42 printf("creat thread5 error:errno is %d,error info is %s\n",ret,strerror(re t)); 43 sleep(1); 44 tmp = pthread_detach(tid5);//線程分離 45 if(0 == tmp) 46 { 47 printf("thread5 is detached success\n"); 48 } 49 else 50 { 51 printf("thread5 is detached failure\n"); 52 tmp = pthread_join(tid5,NULL);//線程等待 53 if(0 == tmp) 54 printf("thread5 wait success\n"); 55 else 56 printf("thread5 wait failure\n"); 57 } 58 return 0; 59 }
運行結果: