「前言」文章是關(guān)于Linux多線程方面的知識,講解會比較細(xì),下面開始!
「歸屬專欄」Linux系統(tǒng)編程
「主頁鏈接」個人主頁
「筆者」楓葉先生(fy)
「楓葉先生有點文青病」「每篇一句」?
我與春風(fēng)皆過客,
你攜秋水?dāng)埿呛印?/span>
——網(wǎng)絡(luò)流行語,詩詞改版用現(xiàn)在的話來說:我不再喜歡你了
目錄
一、??進程地址空間的第三次理解
二、線程概念
2.1 理解線程概念
2.2 線程控制---線程的創(chuàng)建
2.3 Linux進程 VS 線程
2.4?線程的優(yōu)點
2.5 線程的缺點
2.6?線程異常
2.7?線程用途
一、??進程地址空間的第三次理解
在談線程之前,需要再次理解進程地址空間,為后序講解線程做準(zhǔn)備。
- 進程地址空間在進程概念篇章已經(jīng)說過一部分,這是進程地址空間的第一次理解,點擊穿越
- 之后進程地址空間在進程信號篇章已經(jīng)說過一部分,這是進程地址空間的第二次理解,點擊穿越
在這里,是關(guān)于進程地址空間的第三次理解,重點: 虛擬地址到物理地址的轉(zhuǎn)換
如何看待地址空間和頁表?
- 地址空間是進程能看到的資源窗口
- 而頁表決定進程正真擁有資源的情況
所以,合理的對地址空間 + 頁表進行資源劃分,我們就可以對一個進程所有的資源進行分類,合理對資源進行分類后(堆區(qū),共享區(qū)、棧區(qū)等)就可以通過頁表進行映射到不同的物理內(nèi)存區(qū)域
下面對頁表進行介紹:
頁表中除了要有虛擬地址和與其映射的物理地址以外,實際上還有存儲著其他的信息:
- 是否命中
- RWX權(quán)限(讀寫執(zhí)行權(quán)限)
- U/K權(quán)限,U:user(用戶級權(quán)限),K:kernel(內(nèi)核級權(quán)限)
比如我們之前在進程地址空間的第二次理解時,所說的用戶級頁表和內(nèi)核級頁表,實際就是通過 U/K 權(quán)限進行區(qū)分的,U就是用戶級頁表,K就是內(nèi)核級頁表,頁表的每一行可以稱為一個條目,如圖:
以32位平臺為例,在32位平臺下一共有?2^32 個地址,如果頁表單純只是映射物理內(nèi)存,那么這張表就需要建立 2^32 個虛擬地址和物理地址之間的映射關(guān)系,即這張表一共有 2^32 個映射條目
假設(shè)物理地址為4字節(jié),是否命中、RWX權(quán)限、U/K權(quán)限各占一個字節(jié),加起來最少都有 7個字節(jié)了,就按8字節(jié)算
這張頁表一共有 2^32 個映射條目,就意味著存儲這張頁表我們需要用 2^32 * 8 個字節(jié),2^32 是 4GB,最后結(jié)果是 32GB。而在32位平臺下,我們的內(nèi)存是 4GB,壓根就存不下這張頁表。也就是說,頁表映射并不是這樣簡單的映射計算
下面講解虛擬內(nèi)存到物理內(nèi)存的轉(zhuǎn)換規(guī)則(以32位平臺為例)
?每個虛擬地址都是 32個比特位,即 0000 0000 0000 0000 0000 0000 0000 0000,頁表映射的過程為:
- 選擇虛擬地址的前10個比特位在頁目錄當(dāng)中進行查找,找到對應(yīng)的頁表(頁目錄用于索引相應(yīng)的頁表),2^10字節(jié) = 1KB,存儲頁目錄所需的內(nèi)存是 1KB,且頁目錄只有一張
- 再選擇虛擬地址中間的10個比特位在對應(yīng)的頁表當(dāng)中進行查找,找到物理內(nèi)存中對應(yīng)頁框的起始地址,每張頁表的大小也是?2^10字節(jié) = 1KB,頁表是有多張的
- 最后將虛擬地址中剩下的12個比特位作為偏移量從對應(yīng)頁框的起始地址處向后進行偏移,找到物理內(nèi)存中某一個對應(yīng)的字節(jié)數(shù)據(jù),2^12字節(jié) = 4KB,與物理內(nèi)存的頁框?qū)?yīng)
說明:
- 之前的篇章也談過,物理內(nèi)存是被劃分為以 4KB 為大小的塊,這個塊稱為頁框;磁盤的也是被劃分為以 4KB 為大小的塊,這個塊稱為頁幀
- 我們編寫好的可執(zhí)行程序,也是被劃分為以 4KB 為單位進行存儲,加載到內(nèi)存也是以 4KB 為單位進行加載
- 4KB 實際上就是 2^12 個字節(jié),一個頁框的大小也是?2^12 個字節(jié)
- 訪問內(nèi)存的基本大小是1字節(jié),因此一個頁框中就有 2^12 個地址,
- 我們將后 12個比特位作為偏移量,從頁框的起始地址處開始向后進行偏移(頁表中存著每個頁框的起始地址),從而找到物理內(nèi)存對應(yīng)的物理地址
- 頁目錄可以存儲 2^10 個頁表的地址,也就是說最多有 2^10 個頁表
- 頁目錄為一級頁表,頁目錄存儲的每張頁表的地址,這些頁表稱為二級頁表
?實際上,我們需要用到頁表時,OS才會為我們創(chuàng)建相應(yīng)的頁表,不用的話就不創(chuàng)建,這就大大節(jié)省了內(nèi)存空間,即便全部加載全部的頁表,也不會占用太多的內(nèi)存空間
虛擬內(nèi)存到物理內(nèi)存映射過程,都是由 MMU(Memory Management Unit)這個硬件完成的,該硬件是集成在CPU內(nèi)的。頁表是一種軟件,MMU是一種硬件,所以計算機進行虛擬地址到物理地址的轉(zhuǎn)化采用的是軟硬件結(jié)合的方式
這就是虛擬內(nèi)存到物理內(nèi)存的映射規(guī)則
修改常量字符串為什么會觸發(fā)段錯誤??
當(dāng)我們要修改一個字符串常量時,虛擬地址必須經(jīng)過頁表映射找到對應(yīng)的物理內(nèi)存,而在查表過程中發(fā)現(xiàn)其權(quán)限是只讀的,此時你要對其進行修改就會在MMU內(nèi)部觸發(fā)硬件錯誤,操作系統(tǒng)在識別到是哪一個進程導(dǎo)致的之后,就會給該進程發(fā)送信號對其進行終止
二、線程概念
2.1 理解線程概念
線程概念如下:?
- 在一個程序里的一個執(zhí)行路線(執(zhí)行流)就叫做線程(thread)或者是線程是進程內(nèi)的一個執(zhí)行流。更準(zhǔn)確的定義是:線程是 “一個進程內(nèi)部的控制序列”
- 一切進程至少都有一個執(zhí)行線程
- 線程在進程內(nèi)部運行,本質(zhì)是在進程地址空間內(nèi)運行
- 在Linux系統(tǒng)中,在CPU眼中,看到的PCB都要比傳統(tǒng)的進程更加輕量化
- 透過進程虛擬地址空間,可以看到進程的大部分資源,將進程資源合理分配給每個執(zhí)行流,就形成了線程執(zhí)行流
再談進程,以進程為切入點理解這些概念
- 之前的篇章我們所講的:進程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進程對應(yīng)的代碼和數(shù)據(jù)
- 一個進程的創(chuàng)建實際上伴隨著PCB(進程控制塊,Linux是 task_struct)、地址空間(mm_struct)、頁表的創(chuàng)建
- 每個進程都有自己獨立的 task_struct 和 mm_struct、頁表
- 虛擬內(nèi)存決定了進程所能看到的 “資源”
下面只創(chuàng)建 task_struct,并要求創(chuàng)建出來的 task_struct 和原來的 task_struct 共享進程地址空間和頁表,創(chuàng)建的結(jié)果如下:
事實上,我們所創(chuàng)建的其實就是三個線程,只不過這三個線程共用一張 mm_struct 和 一張頁表,這三個 task_struct 就是三個不同的執(zhí)行流
- 線程被創(chuàng)建之后,我們可以虛擬地址空間 + 頁表的方式對原先的進程進行資源劃分,劃分相應(yīng)的資源給線程,所以這里的其中的一個 “進程” 的執(zhí)行力度,一定要比之前所談的一個進程的執(zhí)行力度要細(xì)
- 之前的篇章所談的一個進程是獨占一個虛擬地址空間和頁表和與之對應(yīng)的物理內(nèi)存,現(xiàn)在在這里,這個進程的對應(yīng)的虛擬地址空間、頁表和與之對應(yīng)的物理內(nèi)存資源被幾個 task_struct 所劃分(被線程劃分),所以在這里的一個 “進程” 的執(zhí)行力度要比之前獨占全部資源的進程執(zhí)行力度要細(xì)
思考:
拋開上面的,假設(shè) OS 要真的設(shè)計 “線程” 這個概念,那 OS 就必須對 “線程” 進行管理,如何管理?先描述,再組織。先描述就是為線程設(shè)計專門的數(shù)據(jù)結(jié)構(gòu) TCB(Task Control Block,線程控制塊),用來表示線程對象
專門設(shè)計線程這個概念,常見的系統(tǒng)有:Windows,專門設(shè)計線程概念的結(jié)果是:除了要維護進程與進程之間的關(guān)系,還要維護進程與線程之間的關(guān)系,還要維護線程與線程之間的關(guān)系,并且它們代碼之間的耦合度極高,帶來的結(jié)果就是它們之間的關(guān)系極其復(fù)雜,維護成本極高
- 一個線程被創(chuàng)建的目的是:被執(zhí)行被調(diào)度,以此衍生:線程一定要有(id,狀態(tài),優(yōu)先級,上下文,棧等等)相關(guān)的概念
- 可以看出,單單就 “線程” 被調(diào)度而言,線程與進程就存在許多的重疊(id,狀態(tài),優(yōu)先級,上下文,棧等等)
- 所以,Linux的工程師,不想給 “線程” 設(shè)計相應(yīng)的數(shù)據(jù)結(jié)構(gòu),而是直接復(fù)用 PCB,用 PCB 來表示 Linux 內(nèi)部的 “線程”
所以,在Linux中創(chuàng)建線程,只需創(chuàng)建相應(yīng)的 PCB 即可,所以在Linux中,線程是在進程的內(nèi)部 “運行”,線程在該進程的地址空間內(nèi) “運行”,擁有該進程的一部分資源
那現(xiàn)在應(yīng)如何看待進程?
之前談的進程是:進程 = 內(nèi)核數(shù)據(jù)結(jié)構(gòu) + 進程對應(yīng)的代碼和數(shù)據(jù),現(xiàn)在要以全新的視角看待進程:內(nèi)核視角
以內(nèi)核的視角看待進程:進程是承擔(dān)分配系統(tǒng)資源的基本實體
- Linux進程 = 大量的task_struct + 一個虛擬地址空間 + 頁表 + 一部分的物理內(nèi)存
- 我們之前篇章所談的進程 = 一個task_struct + 一個虛擬地址空間 + 頁表 + 一部分的物理內(nèi)存
- 一個進程的創(chuàng)建:必定要花費相應(yīng)的資源
那如何看待我們之前篇章所學(xué)習(xí)的進程概念,與今天的所講的進程沖突嗎??
答案是不沖突,是互相補充的
- 之前所談的進程也是承擔(dān)分配系統(tǒng)資源的基本實體,只不過,該進程的內(nèi)部只有一個執(zhí)行流(一個 task_struct)
- 而現(xiàn)在所談的進程也是承擔(dān)分配系統(tǒng)資源的基本實體,只不過,該進程內(nèi)部有多個執(zhí)行流(多個 task_struct)
- 進程的內(nèi)部允許只有自己一個執(zhí)行流,也可以允許有多個執(zhí)行流,我們之前所學(xué)的進程,內(nèi)部只有一個執(zhí)行流
- 以前所講的 “進程” 只是一個子集,今天所講的進程才是全貌
??在Linux中,什么是線程?線程是CPU調(diào)度的基本單位
站在CPU的角度,歷史調(diào)度的進程 VS 今天調(diào)度的進程?
- 歷史調(diào)度的進程,也就是我們之前篇章所談的被調(diào)度的進程,被CPU調(diào)度的是:一個進程
- 今天調(diào)度的進程,被CPU調(diào)度的是:進程內(nèi)部的一個分支
實際上,CPU并不關(guān)心你是進程(只有一個執(zhí)行流)還是線程(進程內(nèi)部有多個執(zhí)行流中的一個執(zhí)行流),只要你給了 task_struct,CPU都是無腦執(zhí)行,所以站在CPU的角度:看待它們都是一個輕量級進程??!CPU看來沒有所謂進程、線程,CPU眼里只有輕量級進程
概念總結(jié)?
(0)以例子比喻線程:一個家庭(進程),家庭成員(線程)?,線程是進程的一個子集
(1)在Linux內(nèi)核中有沒有真正意義上的線程?
答案是沒有,Linux是用進程PCB 來模擬線程的,是一種完全屬于自己的一套方案?
(2)站在CPU的視角,每一個 PCB,都可以稱之為輕量級線程
(3)Linux線程是CPU調(diào)度的基本單位,而進程是承擔(dān)分配系統(tǒng)資源的基本單位
(3)進程用來整體申請資源,線程向進程申手要資源
Linux是用進程PCB 來模擬線程有什么好處?簡單,維護成本大大降低,可靠性高效
- 與之相反,真正意義上設(shè)置了線程概念的Windows系統(tǒng),它的維護成本極高,進程與線程之間的關(guān)系極其復(fù)雜,它伴隨的就是問題多,可靠性降低。
- 帶來的結(jié)果就是:Linux服務(wù)器開幾年都不用關(guān)閉,依舊流暢,而Windows開機一段時間后,很可能就會卡死,很大原因是這個
Linux內(nèi)核中有沒有真正意義上的線程的缺點
OS 只認(rèn)線程,用戶(程序員)也只認(rèn)線程,沒有輕量級進程的概念,所以Linux無法直接提供線程操作的系統(tǒng)調(diào)用接口,而只能給我們提供輕量級進程的接口。
- 但是用戶只認(rèn)線程,所以Linux給我們提供了一個線程庫,這個庫是用戶級線程庫,它底層是調(diào)用輕量級進程的接口的,這個線程庫對這些接口進行封裝,上層用戶使用這個庫看起來像是Linux擁有線程一樣
- 這個線程庫的名字叫 pthread,是用戶級線程庫
- 任何的Linux系統(tǒng),必須要提供這個庫,這個線程庫是默認(rèn)攜帶的,這個線程庫也稱為原生線程庫
- 所以用戶需要關(guān)心這個線程庫所提供的接口,不用關(guān)心底層的接口
下面進行對線程的測試,先見一下線程是什么樣子的
2.2 線程控制---線程的創(chuàng)建
創(chuàng)建的線程需要用到的函數(shù)是 pthread_create,man 3?pthread_create 進行查看:
解釋:?
函數(shù):pthread_create
作用: pthread_create - create a new thread(創(chuàng)建一個新線程)
頭文件:#include <pthread.h>
函數(shù)原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
參數(shù)
第一個參數(shù)thread,代表線程ID,是一個輸出型參數(shù),pthread_t是一個無符號整數(shù)
第二次參數(shù)attr,用于設(shè)置創(chuàng)建線程的屬性,傳入空表示使用默認(rèn)屬性
第三個參數(shù)start_routine,是一個函數(shù)的地址,該參數(shù)表示新線程啟動后要跳轉(zhuǎn)執(zhí)行的代碼
第四個參數(shù)arg,是start_routine函數(shù)的參數(shù),用于傳入
返回值
成功返回0,失敗返回錯誤碼
?第三個參數(shù)說明:void *(*start_routine) (void *)
- 該參數(shù)是一個函數(shù)指針,用于設(shè)置一個回調(diào)函數(shù)start_routine
- 該函數(shù)的返回值是 void*,
- 函數(shù)參數(shù)是 void*,該參數(shù)由第四個參數(shù) arg 傳入
測試代碼:
展示主線程和新線程同時運行,主線程創(chuàng)建新線程,主線程創(chuàng)建完新線程后繼續(xù)執(zhí)行,新線程也繼續(xù)執(zhí)行
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//新線程
void* start_routine(void* args)
{
while(1)
{
cout << "我是新線程,我正在運行..." << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread one");//該參數(shù)的內(nèi)容 thread one 傳遞給args
//主線程
while(1)
{
cout << "我是主線程,我正在運行..." << endl;
sleep(1);
}
return 0;
}
?編譯結(jié)果如下,找不到該函數(shù)
原因是我們編譯是沒有鏈接線程庫 pthread,從這里也說明了Linux沒有線程相關(guān)的系統(tǒng)調(diào)用,Linux沒有線程概念,我們需要鏈接該線程庫才可以使用庫中所提供的函數(shù),如何鏈接?在編譯時需要加上 -lpthread
動靜態(tài)庫如何使用,在基礎(chǔ)IO篇章已經(jīng)介紹過了
?添加如下:
再次編譯就可以了
ldd查看可執(zhí)行程序鏈接的庫,確實鏈接了 pthread.so庫,.so是動態(tài)庫
進行運行,發(fā)現(xiàn)主線程和新線程在同時運行
ps 查看,發(fā)現(xiàn)只有一個進程的PID
ps axj | head -1 && ps axj | grep mytest | grep -v grep
對進程發(fā)送9號信號,發(fā)現(xiàn)主線程和新線程都被終止了。信號是直接跟進程直接關(guān)聯(lián),與線程沒有直接關(guān)系。進程只要收到信號,假設(shè)信號是終止信號,進程里面的線程都會與進程一起被終止
上面我們看到的只有一個進程,要查看輕量級進程如何進行查看??
查看輕量級線程相關(guān)信息的命令:ps -aL?
運行程序,進行查看。其中,LWP(Light Weight Process)就是所謂的輕量級進程
- 主線程的PID與輕量級進程ID是一樣的
- 每個輕量級進程的PID都是一樣的
- ?每個輕量級進程LWP的ID都是不一樣的
所以,CPU在調(diào)度的時候,是以 LWP 的ID作為唯一的標(biāo)識符用來標(biāo)識一個執(zhí)行流的,并不是使用PID?
現(xiàn)在把線程的代碼注釋掉,只留下主線程代碼,主線程代碼就是我們以前寫的代碼,內(nèi)部只有一個執(zhí)行流,單進程
編譯運行,ps -aL 進行查看,PID = LWP,這就是我們以前寫的單執(zhí)行流的代碼,只有一個執(zhí)行流的時候,PID和LWP是等價的
注意:信號是整體給進程發(fā)送的,不能單獨發(fā)給一個 LWP?
下面進行證明,pthred_create 的第四個參數(shù)是給第三個參數(shù)發(fā)送的
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//新線程
void* start_routine(void* args)
{
//接收參數(shù)
const char* name = (const char*)args;
while(1)
{
cout << "我是新線程,我正在運行... " << name << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread one");//該參數(shù)的內(nèi)容 thread one 傳遞給args
//主線程
while(1)
{
cout << "我是主線程,我正在運行..." << endl;
sleep(1);
}
return 0;
}
?編譯運行,新線程確實接收到了該參數(shù),所以我們想給新線程傳參的話,可以寫到第四個參數(shù)里面
pthread_create 函數(shù)的第一個參數(shù)是一個輸出型參數(shù),返回的是線程ID,pthread_t 是一個無符號整數(shù)。下面進行驗證,該參數(shù)輸出的是什么
typedef unsigned long int pthread_t;
修改代碼
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//新線程
void* start_routine(void* args)
{
//接收參數(shù)
const char* name = (const char*)args;
while(1)
{
cout << "我是新線程,我正在運行... " << name << endl;
sleep(1);
}
}
int main()
{
//typedef unsigned long int pthread_t;
pthread_t tid;//用于接受返回的線程ID
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread one");//該參數(shù)的內(nèi)容 thread one 傳遞給args
//主線程
while(1)
{
char tidbuffer[64];
snprintf(tidbuffer, sizeof(tidbuffer), "0x%x", tid);
cout << "我是主線程,我正在運行... " << "返回新線程的tid是:" << tid << " 對它取地址:" << tidbuffer << endl;
sleep(1);
}
return 0;
}
編譯運行,十進制打印的結(jié)果無意義,這個 tid 是其實一個地址,與我們在系統(tǒng)里面查的 LWP 的 tid 不一樣,所以這個 tid 與 LWP的 tid 沒有關(guān)系,至于這里的 tid 到底是什么,下面進程控制再談
這就是線程控制之線程創(chuàng)建
2.3 Linux進程 VS 線程
進程是承擔(dān)分配系統(tǒng)資源的基本實體,線程是調(diào)度的基本單位。線程共享進程數(shù)據(jù),但也擁有自己的一部分?jǐn)?shù)據(jù)
?那什么資源是線程私有的呢?(第2、3點最重要)
- 線程ID,PCB屬性私有
- 一組寄存器(上下文數(shù)據(jù))
- 獨立棧結(jié)構(gòu)(每個線程都有臨時的數(shù)據(jù),需要壓棧出棧)(注:這個后序詳細(xì)解釋)
- errno(C語言提供的全局變量,每個線程都有自己獨立errno)
- 信號屏蔽字
- 調(diào)度優(yōu)先級
進程的多個線程共享同一地址空間,因此Text Segment(代碼段)、Data Segment(數(shù)據(jù)段)都是共享的.
- 如果定義一個函數(shù),在各線程中都可以調(diào)用;
- 如果定義一個全局變量,在各線程中都可以訪問到
除此之外,各線程還共享以下進程資源和環(huán)境:
- 文件描述符表(進程打開一個文件后,其他線程也能夠看到)
- 每種信號的處理方式(SIG_ IGN、SIG_ DFL或者自定義的信號處理函數(shù))
- 當(dāng)前工作目錄
- 用戶id和組id
進程和線程的關(guān)系如下圖:
說明:
- 沒有學(xué)線程之前,我們所寫的代碼都是單線程進程
與進程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多,少哪些?
- ?進程切換:需要切換頁表 && 切換虛擬地址空間 && 切換PCB && 切換上下文數(shù)據(jù)
- 線程切換:需要切換PCB && 切換上下文數(shù)據(jù)
- 線程切換cache 不用更新太多,但是進程切換需要全部更新cache(主要體現(xiàn)在這點)
- cache是集成在CPU里面的,是一個硬件,是CPU很重要的組成部分,它具有數(shù)據(jù)保存的功能,它的緩存速度比寄存器慢,比內(nèi)存快
- 寄存器讀取數(shù)據(jù)是直接在 cache 里面讀取的,不是直接從內(nèi)存讀取
- 一個進程只有運行一段時間后,cache 里面才會緩存大量的熱點數(shù)據(jù)
- 熱點數(shù)據(jù)就是:進程經(jīng)常使用、經(jīng)常訪問、經(jīng)常命中的數(shù)據(jù)(需要進程跑一段時間才會存在大量的熱點數(shù)據(jù)),熱點數(shù)據(jù)是被整個進程共享的
- 線程切換的時候,cache內(nèi)緩存的數(shù)據(jù)不用切換,線程需要用到新的數(shù)據(jù)直接緩存進cache即可
- 而進程切換,cache內(nèi)緩存的數(shù)據(jù)需要全部切換,新切換的進程需要重新緩存數(shù)據(jù),這樣效率就比線程慢得多了
2.4?線程的優(yōu)點
- 創(chuàng)建一個新線程的代價要比創(chuàng)建一個新進程小得多
- 與進程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多
- 線程占用的資源要比進程少很多
- 能充分利用多處理器的可并行數(shù)量
- 在等待慢速I/O操作結(jié)束的同時,程序可執(zhí)行其他的計算任務(wù)
- 計算密集型應(yīng)用,為了能在多處理器系統(tǒng)上運行,將計算分解到多個線程中實現(xiàn)
- I/O密集型應(yīng)用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
說明:
- 計算密集型:執(zhí)行流的大部分任務(wù),主要以計算為主。比如加密解密、大數(shù)據(jù)查找、算法等
- IO密集型:執(zhí)行流的大部分任務(wù),主要以IO為主。比如刷磁盤、訪問數(shù)據(jù)庫、訪問網(wǎng)絡(luò)等
- 現(xiàn)代多核CPU一般指:CPU內(nèi)部集成了多個運算器
- CPU的核數(shù)決定了線程的個數(shù),CPU的個數(shù)決定了進程的個數(shù)
2.5 線程的缺點
- 性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與共它線程共享同一個處理器。如果計算密集型線程的數(shù)量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開銷,而可用的資源不變。
- 健壯性降低:編寫多線程需要更全面更深入的考慮,在一個多線程程序里,因時間分配上的細(xì)微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。
- 缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調(diào)用某些OS函數(shù)會對整個進程造成影響。
- 編程難度提高:編寫與調(diào)試一個多線程程序比單線程程序困難得多
線程的健壯性降低,下面進行測試
測試代碼,主線程正常運行,新線程發(fā)生空指針異常
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
//新線程
void* start_routine(void* args)
{
//static_cast 安全的進行強制類型轉(zhuǎn)換,C++11
string name = static_cast<const char*>(args);
while(1)
{
cout << "new thread create success, name: " << name << endl;
sleep(1);
//一個線程出問題,會影響其他線程么?
int *p = nullptr;
*p = 0;//空指針異常
}
}
int main()
{
pthread_t tid;//用于接受返回的線程ID
int n = pthread_create(&tid, nullptr, start_routine, (void*)"thread new");//該參數(shù)的內(nèi)容 thread one 傳遞給args
//主線程
while(1)
{
cout << "new thread create success, name: main thread" << endl;
sleep(1);
}
return 0;
}
編譯運行,線程全部終止了,ps -aL 查看也沒有了,所以線程出異常,會直接影響其他線程的運行,說明線程的健壯性或魯棒性較差
注意:信號是叫做進程信號,是整體發(fā)給進程的
2.6?線程異常
- 單個線程如果出現(xiàn)除零,野指針問題導(dǎo)致線程崩潰,進程也會隨著崩潰
- 線程是進程的執(zhí)行分支,線程出異常,就類似進程出異常,進而觸發(fā)信號機制,終止進程,進程終止,該進程內(nèi)的所有線程也就隨即退出
2.7?線程用途
- 合理的使用多線程,能提高CPU密集型程序的執(zhí)行效率
- 合理的使用多線程,能提高IO密集型程序的用戶體驗(如生活中我們一邊寫代碼一邊下載開發(fā)工具,就是多線程運行的一種表現(xiàn))
線程概念完結(jié),下一篇進入線程控制文章來源:http://www.zghlxwxcb.cn/news/detail-439523.html
--------------------- END ----------------------文章來源地址http://www.zghlxwxcb.cn/news/detail-439523.html
「 作者 」 楓葉先生
「 更新 」 2023.4.27
「 聲明 」 余之才疏學(xué)淺,故所撰文疏漏難免,
或有謬誤或不準(zhǔn)確之處,敬請讀者批評指正。
到了這里,關(guān)于『Linux』第九講:Linux多線程詳解(一)_ 線程概念 | 線程控制之線程創(chuàng)建 | 虛擬地址到物理地址的轉(zhuǎn)換的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!