【Linux系统编程学习】Linux线程控制原语
此為牛客Linux C++課程筆記。
0. 關(guān)于線程
注意:LWP號和線程id不同, LWP號是CPU分配時間片的依據(jù),線程id是用于在進(jìn)程內(nèi)部區(qū)分線程的。
1. 線程與進(jìn)程的區(qū)別
對于進(jìn)程來說,相同的地址(同一個虛擬地址)在不同的進(jìn)程中,反復(fù)使用而不沖突。原因是他們雖虛擬址一樣,但,頁目錄、頁表、物理頁面各不相同。相同的虛擬址,映射到不同的物理頁面內(nèi)存單元,最終訪問不同的物理頁面。
但!線程不同!兩個線程具有各自獨立的PCB,但共享同一個頁目錄,也就共享同一個頁表和物理頁面。所以兩個PCB共享一個地址空間。
實際上,無論是創(chuàng)建進(jìn)程的fork,還是創(chuàng)建線程的pthread_create,底層實現(xiàn)都是調(diào)用同一個內(nèi)核函數(shù)clone。
如果復(fù)制對方的地址空間,那么就產(chǎn)出一個“進(jìn)程”;如果共享對方的地址空間,就產(chǎn)生一個“線程”。
因此:Linux內(nèi)核是不區(qū)分進(jìn)程和線程的。只在用戶層面上進(jìn)行區(qū)分。所以,線程所有操作函數(shù) pthread_* 是庫函數(shù),而非系統(tǒng)調(diào)用。
優(yōu)點: 1. 提高程序并發(fā)性 2. 開銷小 3. 數(shù)據(jù)通信、共享數(shù)據(jù)方便
缺點: 1. 庫函數(shù),不穩(wěn)定 2. 調(diào)試、編寫困難、gdb不支持 3. 對信號支持不好
優(yōu)點相對突出,缺點均不是硬傷。Linux下由于實現(xiàn)方法導(dǎo)致進(jìn)程、線程差別不是很大。
2. 線程相關(guān)操作函數(shù)
2.1 獲取線程id
#include <pthread.h> pthread_t pthread_self(void);功能:獲取線程ID。其作用對應(yīng)進(jìn)程中 getpid() 函數(shù)。
2.2 創(chuàng)建線程: pthread_create
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);功能:創(chuàng)建一個子線程
參數(shù):
- thread:傳出參數(shù),線程創(chuàng)建成功后,子線程的線程ID被寫到該變量中。
- attr : 設(shè)置線程的屬性,一般使用默認(rèn)屬性,即NULL
- start_routine : 函數(shù)指針,這個函數(shù)是子線程需要處理的邏輯代碼
- arg : 給第三個參數(shù)(回調(diào)函數(shù))使用,是回調(diào)函數(shù)的參數(shù)
返回值:
- 成功:0
- 失敗:返回錯誤號。這個錯誤號和之前errno不太一樣,獲取錯誤號的信息使用:
創(chuàng)建線程示例代碼如下:
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>void* callback(void* arg) {printf("its child thread, thread id is %lu\n", pthread_self());printf("arg = %d\n", *(int *)arg); }int main() {pthread_t pid;int a = 5;int ret = pthread_create(&pid, NULL, callback, &a);if(ret != 0) {// 說明創(chuàng)建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("its main thread, thread id is %lu\n", pthread_self());sleep(1);return 0; }發(fā)現(xiàn)無法編譯
查閱文檔發(fā)現(xiàn):
編譯時加-pthread即可,運行結(jié)果如下:
2.3 終止線程: pthread_exit
注意,不能使用exit函數(shù)終止當(dāng)前線程,exit將終止當(dāng)前進(jìn)程,進(jìn)程中的所有線程將一并終止。
#include <pthread.h> void pthread_exit(void *retval);參數(shù):retval表示線程退出狀態(tài),通常傳NULL
多線程環(huán)境中,應(yīng)盡量少用,或者不使用exit函數(shù),取而代之使用pthread_exit函數(shù),將單個線程退出。任何線程里exit導(dǎo)致進(jìn)程退出,其他線程未工作結(jié)束,主控線程退出時不能return或exit。
2.4 連接已終止的線程(回收線程):pthread_join
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);功能:和一個已經(jīng)終止的線程進(jìn)行連接(回收子線程的資源)
注意:這個函數(shù)是阻塞函數(shù),調(diào)用一次只能回收一個子線程,一般在主線程中使用
參數(shù):
- thread:需要回收的子線程的ID
- retval: 接收子線程退出時的返回值(即pthread_exit的void *retval參數(shù)), 而且是傳出參數(shù)。
返回值:0 : 成功;非0 : 失敗,返回的錯誤號
不使用傳出參數(shù)的一個簡單使用如下:
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>void* callback(void* arg) {printf("子線程運行中...\n");sleep(2); }int main() {pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {// 說明創(chuàng)建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_join(pid, NULL);printf("子線程已回收\n");return 0; }子線程執(zhí)行2秒后,主進(jìn)程才輸出“子線程已回收”,說明pthread_join函數(shù)是阻塞的。
pthread_join函數(shù)比較難以理解的地方是他的第二個參數(shù):void **retval,是void二級指針類型,這是因為:
首先這個參數(shù)是想接收pthread_exit所傳出的void *retval, 這個參數(shù)本身是void *的一級指針類型,而pthread_join函數(shù)的void **retval在設(shè)計時是設(shè)計成一個傳出參數(shù)的,以便把pthread_exit傳出的void *retval帶回主線程,所以要想把 void * 類型變量設(shè)計成傳出參數(shù),即是 void **。
示例程序如下:
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>int value = 10;void* callback(void* arg) {printf("子線程運行中...\n");pthread_exit((void *)&value); }int main() {pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {// 說明創(chuàng)建失敗char * errstr = strerror(ret);printf("error: %s\n", errstr);}int *thread_retval; // 給pthread_join調(diào)用,接收pthread_exit的傳出參數(shù)pthread_join(pid, (void **)&thread_retval);printf("exit data : %d\n", *thread_retval);return 0; }運行結(jié)果如下:
2.5 線程分離:pthread_detach
#include <pthread.h> int pthread_detach(pthread_t thread);功能:使進(jìn)程處于分離狀態(tài)。被分離的線程在終止的時候,會自動釋放資源返回給系統(tǒng),避免產(chǎn)生僵尸線程。
線程分離狀態(tài):指定該狀態(tài),線程主動與主控線程斷開關(guān)系。線程結(jié)束后,其退出狀態(tài)不由其他線程獲取,而直接自己自動釋放。網(wǎng)絡(luò)、多線程服務(wù)器常用。
參數(shù):需要分離的線程的ID
返回值:成功:0,失敗:返回錯誤號
注意:
2.6 線程取消:pthread_cancel
#include <pthread.h> int pthread_cancel(pthread_t thread);功能:取消線程(讓線程終止)
【注意】:線程的取消并不是實時的,而有一定的延時。需要等待線程到達(dá)某個取消點(檢查點)。
類似于玩游戲存檔,必須到達(dá)指定的場所(存檔點,如:客棧、倉庫、城里等)才能存儲進(jìn)度。殺死線程也不是立刻就能完成,必須要到達(dá)取消點。
取消點:是線程檢查是否被取消,并按請求進(jìn)行動作的一個位置。通常是一些系統(tǒng)調(diào)用creat,open,pause,close,read,write…
執(zhí)行命令man 7 pthreads可以查看具備這些取消點的系統(tǒng)調(diào)用列表。也可參閱 APUE.12.7 取消選項小節(jié)。
可粗略認(rèn)為一個系統(tǒng)調(diào)用(進(jìn)入內(nèi)核)即為一個取消點。如線程中沒有取消點,可以通過調(diào)用pthreestcancel函數(shù)自行設(shè)置一個取消點。
看下面這個代碼示例,子線程無限循環(huán):
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>void* callback(void* arg) {while(1) {printf("子線程運行中...\n");sleep(1);}return NULL; }int main() {pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_cancel(pid);ret = pthread_join(pid, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("線程已回收\n");return 0; }運行后成功輸出”線程已回收“, 這是因為pthread_cancel終止了子線程的運行,故pthread_join得以執(zhí)行。
但是如果將子進(jìn)程中循環(huán)語句中的內(nèi)容去掉:
#include <stdio.h> #include <pthread.h> #include <string.h> #include <unistd.h>void* callback(void* arg) {while(1) {// printf("子線程運行中...\n");// sleep(1);}return NULL; }int main() {pthread_t pid;int ret = pthread_create(&pid, NULL, callback, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}pthread_cancel(pid);ret = pthread_join(pid, NULL);if(ret != 0) {char * errstr = strerror(ret);printf("error: %s\n", errstr);}printf("線程已回收\n");return 0; }運行以后發(fā)現(xiàn)沒有輸出,主線程阻塞。這是因為子線程的while(1)死循環(huán)中沒有任何語句,也就不會執(zhí)行任何系統(tǒng)調(diào)用,也就不會到達(dá)任何一個“取消點”,所以子線程并沒有被終止,主線程被阻塞在pthread_join處。而之前的代碼循環(huán)語句中的printf會調(diào)用系統(tǒng)調(diào)用write,所以會到達(dá)“取消點”,pthread_join將已經(jīng)結(jié)束的子線程回收。
總結(jié)
以上是生活随笔為你收集整理的【Linux系统编程学习】Linux线程控制原语的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无精症的怎么治疗
- 下一篇: 做试管婴儿植入能放几个胚胎