【Linux】线程
前言
目錄
1.Linux下的線程概念
2.Linux線程控制:pthread線程庫
在單執(zhí)行流的進程中,此執(zhí)行流獨占了進程的所有資源
在一個進程內(nèi)部,有時不一定只有一個執(zhí)行流,在多執(zhí)行流下,多個執(zhí)行流共享了進程的地址空間,我們把“一個程序內(nèi)部的控制序列”叫做線程
線程本質(zhì)是在進程的地址空間內(nèi)運行
進程的切換涉及到頁表映射的切換,而線程的切換只是切換了指令序列而在同一個地址空間中進行
那么我們給出下面兩個重要概念
- 進程是操作系統(tǒng)分配資源的基本實體
- 線程是進程內(nèi)部的一個執(zhí)行流
舉個栗子:
在現(xiàn)實生活中,假如我們把社會資源的基本單位看作是家庭,比如我們經(jīng)常以家庭年收入統(tǒng)計社會財富的分配狀況,那么此時:
- 操作系統(tǒng)—>社會
- 進程—>家庭
- 線程—>家庭成員
家庭成員共享了家庭的資源,家庭成員之間有共享的資源,也有私人的小秘密。
透過進程虛擬地址空間,可以看到進程的大部分資源,將進程資源合理分配給每個執(zhí)行流,就形成了線程執(zhí)行流
線程可以被創(chuàng)建、等待、終止、控制
家庭有生老病死、家規(guī)等等…
1.Linux下的線程概念
在Linux下,其實沒有真正意義上的線程概念,是用進程來模擬的
Linux的進程叫做輕量級進程
LWP是輕量級進程,在Linux下進程是資源分配的基本單位,線程是cpu調(diào)度的基本單位,而線程使用進程pcb描述實現(xiàn),操作系統(tǒng)在創(chuàng)建線程時給每個線程都創(chuàng)建一個pcb結(jié)構(gòu)體,并且同一個進程中的所有pcb共用同一個虛擬地址空間,因此相較于傳統(tǒng)進程更加的輕量化有了更多執(zhí)行流之后。進程變成了分配資源的基本實體,進程一旦被創(chuàng)建好之后,里面可能有多個執(zhí)行流
與進程相比,線程在CPU執(zhí)行時可能更加輕量化:pcb上下文肯定要切換,但是線程的地址空間、頁表不用換;CPU調(diào)度時,看到的是LWP,也就是輕量級進程Light Weight Process
1.1 線程的優(yōu)點
- 創(chuàng)建一個新線程的代價要比創(chuàng)建一個新進程小得多
- 與進程之間的切換相比,線程之間的切換需要OS的工作量更少
- 線程占用的資源比進程少得多
1.2 線程能夠看到進程的所有資源,因為所有PCB都共享地址空間
- 好處:線程間通信成本低
- 壞處:存在大量的臨界資源,勢必需要使用各種互斥和同步機制保證臨界資源的安全
1.3 線程異常
線程是進程的一個執(zhí)行分支,當(dāng)發(fā)生野指針/除0等異常操作導(dǎo)致線程退出的同時,也意味著進程觸發(fā)了該錯誤,操作系統(tǒng)會給進程發(fā)送信號,終止進程。這體現(xiàn)了多線程下魯棒性降低了。
1.4 線程的共享與獨享
線程共享
- 文件描述符表
- 每種信號的處理方式
- 工作目錄
- 用戶id組id
獨有:
- 上下文數(shù)據(jù)(寄存器):體現(xiàn)了多個線程是可以切換的
- 獨立的棧結(jié)構(gòu):體現(xiàn)了線程是獨立運行的,各自的上下文數(shù)據(jù)不會互相影響
2.Linux線程控制:pthread線程庫
首先要強調(diào)一點:pthread庫并不是系統(tǒng)庫,而是Linux下為了模擬線程而采用的第三方庫。本質(zhì)是封裝了對于輕量級進程的某些操作。
鏈接這些線程函數(shù)庫時要使用編譯器命令的“-lpthread”選項
接口:
pthread_create()
pthread_join()
pthread_cancel()
pthread_exit()
pthread_self()
2.1線程的創(chuàng)建
功能: 創(chuàng)建一個新的線程
原型
4個參數(shù)
- thread:返回線程ID,這是進程地址空間的共享區(qū)的地址
- attr:設(shè)置線程的屬性,attr為NULL表示使用默認(rèn)屬性
- start_routine:是個函數(shù)地址,線程啟動后要執(zhí)行的函數(shù)
- arg:傳給線程啟動函數(shù)的參數(shù),如果需要傳入多個參數(shù),可以用結(jié)構(gòu)體封裝
返回值: 成功返回0;失敗返回錯誤碼
讓我們來玩一玩線程的創(chuàng)建,這邊我們在main函數(shù),也就是主線程創(chuàng)建了新線程,又在新線程中創(chuàng)建了另外5個更新的線程,使用ps -aL 命令查看輕量級進程
#include <iostream> #include <pthread.h> #include <unistd.h> using namespace std; void* routine(void* args) {while(true) {cout << "I am a thread" << endl;sleep(1);}return nullptr; } void* ThreadRoute(void* args) {pthread_t tids[5];for(int i = 0; i < 5; i++) {pthread_create(tids+i, nullptr, routine, nullptr);}for(int i = 0; i < 5; i++) {pthread_join(tids[i], nullptr);}return nullptr; } int main() {pthread_t tid; int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");while(true) {std::cout << "I am main process" << std::endl;sleep(2);}return 0; }2.2 線程的終止
- 從自己的例程中return,線程退出
- 主線程退出,進程退出
有三種方法終止某個線程而不終止進程:
線程一般終止之后,main thread等待,不等待會造成僵尸
為防內(nèi)存泄漏,要保證主線程最后退出,讓新線程正常結(jié)束
retral從pcb中提取退出碼
- 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進程的地址空間內(nèi)。
- 創(chuàng)建新的線程不會復(fù)用剛才退出線程的地址空間。
我們精心設(shè)計了除0錯誤:
在進程exit code中,存有退出碼+信號,而信號是針對進程的,線程崩潰進程隨之崩潰
子進程創(chuàng)建的線程的除0錯誤導(dǎo)致OS給子進程發(fā)送信號,子進程的主線程崩潰,退出碼收不到
關(guān)于pthread_cancel
- sleep新線程被創(chuàng)建了但是沒有被調(diào)度你就cancel了,建議一定要讓新線程被調(diào)度跑起來
- cancel具有一定的延時性,并不一定立即執(zhí)行
- cancel建議不要再開頭或結(jié)尾使用
2.3 線程等待
為什么要等待?
- 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進程的地址空間內(nèi)。
- 創(chuàng)建新的線程不會復(fù)用剛才退出線程的地址空間。
功能:等待線程結(jié)束
原型
參數(shù)
- thread:線程ID
- value_ptr:它指向一個指針,后者指向線程的返回值
返回值成功返回0;失敗返回錯誤碼
調(diào)用該函數(shù)的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的
終止?fàn)顟B(tài)是不同的,總結(jié)如下:
我們讓線程return一個new出來的5
#include <iostream> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> using namespace std;void* ThreadRoute(void* args) {int* p = new int(5);cout << "threadID: " << pthread_self() << endl;sleep(2);return (void*)p; } int main() { if(fork() == 0) {pthread_t tid; void* ret;int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");pthread_join(tid, &ret);cout << "ret: " << *(int*)ret << endl;delete (int*)ret;exit(11);}int status;pid_t id = waitpid(-1, &status, 0);cout << "exit code: " << ((status>>8)&0xff) << endl;cout << "sig: " << ((status)&0x7f) << endl;return 0; }2.4 獲取線程ID
pthread_ create函數(shù)會產(chǎn)生一個線程ID,存放在第一個參數(shù)指向的地址中。該線程ID和前面說的線程ID不是一回事。
前面講的線程ID屬于進程調(diào)度的范疇。因為線程是輕量級進程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個數(shù)值來唯一表示該線程。
pthread_ create函數(shù)第一個參數(shù)指向一個虛擬內(nèi)存單元,該內(nèi)存單元的地址即為新創(chuàng)建線程的線程ID,屬于NPTL線程庫的范疇。線程庫的后續(xù)操作,就是根據(jù)該線程ID來操作線程的。
線程庫NPTL提供了pthread_ self函數(shù),可以獲得線程自身的ID:
pthread_t pthread_self(void); #include <iostream> #include <pthread.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> using namespace std;void* ThreadRoute(void* args) {cout << "threadID: " << pthread_self() << endl; } int main() {if(fork() == 0) {pthread_t tid; int thread_id = pthread_create(&tid, nullptr, ThreadRoute, (void*)"I am thread");exit(11);}int status;pid_t id = waitpid(-1, &status, 0);return 0; }2.5 線程分離
線程分離的本質(zhì)是讓主線程不join新線程,不關(guān)心返回值,從而讓新線程退出的時候自動回收
如果一個線程被設(shè)置為分離狀態(tài),他就不該被join
如果你join,結(jié)果就是未定義
即便線程被設(shè)置為分離狀態(tài),但是如果該線程依舊出錯崩潰,還是會影響主線程和其他正常線程 -> 所有線程在同一個地址空間中運行
可以是線程組內(nèi)其他線程對目標(biāo)線程進行分離,也可以是線程自己分離:
pthread_detach(pthread_self());線程分離后,主線程等待不到新線程:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> void* thread_run(void* arg) {pthread_detach(pthread_self());printf("%s\n", (char*)arg);return NULL; } int main(void) {pthread_t tid;if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0) {printf("create thread error\n");return 1;}int ret = 0;sleep(1);//很重要,要讓線程先分離,再等待if (pthread_join(tid, NULL) == 0) {printf("pthread wait success\n");ret = 0;}else {printf("pthread wait failed\n");ret = 1;}return ret; }3.總結(jié)補充
為什么要有pthread原生線程庫?
linux沒有真正的線程,是用進程來模擬的
操作系統(tǒng)是不會直接提供類似的線程創(chuàng)建、退出、分離、等待相關(guān)的system call 接口,但是會提供創(chuàng)建輕量級進程的接口,但是用戶需要有所謂的線程創(chuàng)建、退出、分離、等待相關(guān)的接口啊,所以為了更好的適配輕量級進程的接口,就模擬封裝了一個用戶層原生線程庫NPTL
可是進程是PCB去管理的,用戶層先要以管理線程的辦法來管理輕量級進程,就得知道,線程id,狀態(tài),優(yōu)先級,其他屬性,從而用來進行用戶線程管理!
所以tcb不用內(nèi)核維護,而在用戶層維護
曾經(jīng)的pthead_t 是用戶層的概念,是pthread庫中的地址
相當(dāng)于警察派了幾個線人去犯罪集團當(dāng)臥底,警察不懂行話,但是線人懂,所以線人充當(dāng)了和犯罪分子溝通的角色,而線人反手會把情報用人話反饋給警察
警察只需要來操縱、管理線人,就能得到情報
總結(jié)
- 上一篇: threejs添加天空盒
- 下一篇: 电脑重装系统 Win11 如何打开Dir