linux線程,線程控制與線程相關概念

線程概念

線程這個詞或多或少大家都聽過,今天我們正式的來談一下線程;

在我一開始的概念中線程就是進程的一部分,一個進程中有很多個線程,這個想法基本是正確的,但細節部分呢我們需要細細講解一下;

什么是線程

1.線程是進程執行流中的一部分,就是說線程是進程內部的一個控制序列;

2.線程是操作系統調度的基本單位;

3.在linux中沒有真正意義上的線程,也就是操作系統中說的tcb(thread ctrl block),但是其他的操作系統是有的不同的操作系統實現不同(如windows就是在pcb下再次構建了tcb的數據結構);為什么linux下沒有真正意義的線程呢?因為線程再操作系統中也是需要被管理的,可是線程的管理一定得創建數據結構,創建復雜的數據結構一定需要增加維護的成本與難度,而線程的管理其實和進程是相似的;所以聰明的linux程序員將線程管理設計為了輕量化的進程,將線程與進程統一管理,減輕了代碼的復雜度,便于維護提高效率;(線程粒度細于進程

4.線程其實是進程的一部分,所以線程運行的地方就是在進程的虛擬地址空間中的;因為線程本身也是屬于進程的一部分的,只是被加載到了進程隊列中運行而已;(進程就像是一個家庭,線程就像是家庭中的每一個人,每個人都有自己的工作,所以需要分開執行,也就是處于進程隊列中),進程會分配的資源給線程(家庭中的資源會分配給每個人,比如爸爸要去遠的地方工作需要開車,那車子這個資源就會分配給父親),這個資源包括代碼和數據,之前我們理解的進程可以當作是主線程,通過分配自己的代碼給它內部的線程,內部的線程拿到數據和代碼資源區執行分配給它的工作,從而執行相應的操作;

重談虛擬地址空間

頁表如何映射

計算頁表大小

?所以一個頁表最大為4mb,并且一個頁表的二級頁表不一定為1024個,因為頁表的映射也不是一次就完成的,而已頁表的映射使用完之后還會釋放等;所以一個頁表大小不會大于4mb;

就是這樣的頁表完成了我們的映射;那我們的數據和代碼都是存儲在這個地址空間上的;而函數就是一個現成的地址,所以我們分配給線程代碼數據,是不是可以直接將這個函數分給線程呢?這樣不就等于把線程需要執行的工作劃分給了線程嗎?

所以線程劃分資源本質上是將地址空間中的資源進行分配

為什么我們要創建線程?線程優點

1.同一進程中線程之間的切換更加輕量化;

在我們的內存中最快的是寄存器,,cpu之間拿寄存器中的數據進行計算,寄存器也需要獲取數據,而寄存器不是之間從內存中拿數據的,因為內存相較于寄存器還是太慢了,所以它們之間還有一個cache緩存,這個cache中存放的是當前進程的數據和指令,寄存器可以很快的就從cache中拿到一個進程中的數據(cache命中率會很高,因為都在同一進程,都是熱數據);因為同一進程中的線程是共享數據的,所以cache切換時只需要切換task_struct,而進程之前切換所有數據都需要切換(進程切換了,進程間具有獨立性,cache中的數據一定都需要被切換,所咦數據會變冷重新去命中數據),這樣的切換消耗會大的多;

2.創建和銷毀線程的代價要小很多;因為線程的數據已經在內存中了,線程只需要從它所在的進程中獲取數據即可;

3.io密集型程序,通過多線程可以提高很大的效率,在進行io的時候進程可以讓其他線程進行計算等操作,不需要等待io結束再操作;相比單線程的等待要優化非常多;

4.計算密集型程序,在單核cpu中多線程沒有什么提升,想法,線程之間的切換還會降低效率;但是在多核cpu中,多線程可以在多個核上進行計算(計算線程數要小于等于核的數量),也是大大提高了計算的效率的;

線程缺點:

由于線程之前沒有獨立性,共享進程代碼數據,代碼的健壯性要低一些,所以需要進行同步于互斥;缺乏訪問控制->健壯性低;相應的調試也會更難;

線程數據?

每個線程雖然都是進程的一部分,從進程中獲得數據的,但是線程一定需要包含自己的數據;

線程自己的數據:

1.線程對應的上下文數據(寄存器)

2.線程運行時數據(獨立的棧空間)

3.線程id

4.信號屏蔽字

5.調度優先級

?6.errno

線程操作

上面講解了線程的基本內容,下面我們來對線程進行操作來理解線程;

我們需要先了解這些linux中posix標準中的原生線程庫中的函數;?

線程創建

pthread_create

這個函數是用來創建子線程的;

第一個參數是一個輸出型參數,用來輸出創建線程的tid;

第二個參數是用來設置線程的屬性的,其實這是一個指向線程屬性對象的指針,通過傳遞我們設置好的對象傳遞給線程從而改變線程的默認屬性,一般我們都傳遞NULL使用默認屬性即可;

第三個參數是一個回調函數,用來提供給線程運行的代碼,可以理解為讓線程執行此函數;

第四個參數就是一個傳遞給函數(第三個參數——回調函數)的參數,這個參數既可以是普通的內置類型,也可以是結構體,這樣可以很多的數據;

返回值返回0為成功創建,創建失敗返回返回錯誤碼,不設置errno;

?下面可以看到我們的代碼成功運行了;

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;void *routine(void *data)
{for (int i = 0; i < 5; i++){usleep(100000);cout << "線程1, pid: " << getpid() << endl;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, routine, nullptr);for (int i = 0; i < 7; i++){usleep(200000);cout << "線程0, pid: " << getpid() << endl;}return 0;
}

?從上面的現象我們可以清楚的知道線程是一個獨立的執行流雖然routine函數和main函數它們兩個再同一個程序中且是兩個循環但是,這兩個循環同時跑起來了,所以證明了線程的獨立性;

編譯時需要加-lpthread選項

在linux中使用原生線程庫進行編程時我們編譯選項總是需要帶上-lpthread,這個選項在我們前面學習動靜態庫的時候就很熟悉了,用來連接指定的庫;而似乎我們在以往的編程中除開我們自己創建動靜態庫的情況之外,我們從未出現過主動連接動靜態庫的情況;

為什么我們不需要主動去連接呢?這是因為編譯器自動去幫我們連接了,我們的c,c++語言級別的庫也好,linux的系統庫也罷,它們庫的路徑都是已經存儲在編譯器的配置文件中的,編譯器可以自動的找到庫(第一步),然后編譯器會自動連接這些庫(第二步);為什么會自動連接呢?我們可以認為這些系統庫和標準庫是編譯器自己的庫,所以編譯器會自動的連接;而pthread這個庫是posix標準中的原生線程庫;它是屬于第三方庫的,而第三方庫即使它被放到系統,標準庫的路徑之下,它也是不會被自動連接的;所以我們需要帶上-lpthread選項去主動連接這個庫;

查看線程

我們看到的線程的現象接下來,我們從系統的角度的入手,使用系統的指令來查看我們的線程的體現;

ps -aL

lwp的全稱是light weight process輕量級進程;?

線程的等待與tid獲取函數

pthread_join

子進程被創建,父進程需要等待進程返回,而線程被創建也需要被等待,但是這里只有主線程和其他線程的區別,主線程需要等待其他所有線程,防止內存泄漏的問題;

?這里的第一個參數是指向被等待線程的tid;

第二個參數是一個輸出型參數可以用來接收線程的返回值,這個返回值可以是任意類型的數據(自定義類型也可以);

返回值為0代表等待成功,非0則返回錯誤值,不設置errno碼;

pthread_self

可以獲得線程的tid;

這是一個無參函數和getpid的使用方式是一樣的;

代碼實現?

?知道了這些基本的函數后,我們下面用代碼實踐來展示現象并解釋:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
using namespace std;struct thread_data
{string threadName;string threadReturn;
};void *routine(void *data)
{thread_data *d1 = static_cast<thread_data *>(data);// thread_data *d1 = (thread_data *)(data);int count = 3;for (int i = 0; i < count; i++){printf("tid: %p threadName: %s count: %d\n", pthread_self(), d1->threadName.c_str(), i);sleep(1);}// int a=5/0;//除0錯誤 這里說明了當進程中的某個線程出現異常時,整個進程都會退出// exit(0);//使用exit退出 這里也說明了使用exit會退出整個進程d1->threadReturn="return_"+d1->threadName;return d1;
}void initThread(thread_data *data, int num)
{data->threadName = "thread_" + to_string(num);
}int main()
{pthread_t tid;thread_data *data = new thread_data;initThread(data, 1);int ret_create = pthread_create(&tid, nullptr, routine, (void *)data);void *ret_thread;printf("我是主線程tid: %p\n",pthread_self());pthread_join(tid, &ret_thread);cout << ((thread_data *)ret_thread)->threadReturn << endl;//證明獲得了一個類返回值delete data;return 0;
}

?使用return正常退出的情況:

下面是使用exit和異常退出的情況:?

?

通過代碼和現象我們可以知道這些細節:

1. 我們可以使用join獲取線程的返回值,線程返回值可以為任意類型的指針,所以可以傳遞任意值;

2.我們的子線程退出的時候不能使用exit退出這樣會導致整個進程都退出,我們可以使用return,pthread_exit(后面講),使用cacel取消joined(后面講),這3種方式退出;

3.進程中的任意一個線程出現異常整個進程都會退出

4.線程的tid是一個地址,這個地址是進程堆棧之間的內存區域(通過上面的現象也可清楚的明白)

由此我們可以知道這些函數的大致使用;

線程結構體位置

上面我們通過概念與實現基本的了解了線程,接下來我們通過圖像來了解線程的結構體:

其實我們的線程是這樣存在在我們的進程中的,因為linux程序員為了減輕代碼的維護效率linux中沒有真正的線程,而是將線程作為輕量級進程,而用來描述輕量級進程的結構體是存儲在用戶層的,存儲的位置就是共享區的原生線程庫,線程庫中維護了線程的屬性數據,內核的執行流(tcb控制塊)通過找到進程中的線程庫中的線程結構體從而找到線程代碼執行線程;?

所以線程的屬性是由線程庫來維護的,而tid之所以是共享區之中的代碼的原因就是因為tid指的是共享區中線程庫中的某個線程結構體所在的首地址;

線程空間的特點

1.線程之間的棧空間是獨立的;

這一點非常好理解,因為函數在被調用的時候就會創建自己的棧幀嘛;而線程執行其實就是執行了分給他的函數;所以線程棧空間是獨立的;

2.線程之間是沒有秘密的;

為什么線程之間獨立但是又沒有秘密呢?因為線程總是在一個進程中的嘛,棧之間的數據,只需要通過一個指針就可以獲得了;

代碼示例:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include <string>
using namespace std;struct threadData
{string threadName;threadData(int num){threadName = "thread" + to_string(num);}threadData() = default;
};int *g_index;void *routine(void *args)
{int val = 0;threadData *data = (threadData *)args;for (int i = 1; i <=3 ; i++){printf("%s tid: %p val: %d\n", data->threadName.c_str(), pthread_self(), val);val++;}if(data->threadName=="thread1"){val=10000;g_index=&val;sleep(5);}return (void *)0;
}int main()
{vector<pthread_t> tids;for (int i = 0; i < 3; i++){pthread_t tid;threadData *td = new threadData(i);pthread_create(&tid, nullptr, routine, td);tids.push_back(tid);sleep(1);}cout<<"這是thread2的val值: "<<*g_index<<endl;for(auto t:tids){void *retData;pthread_join(t,&retData);}return 0;
}

但是如果我們想要獲得某個棧空間的數據時這也是可以輕松做到的:

我們在routine函數中加入一段這樣的代碼,并在main函數中讀取數據;

  routine函數中:if(data->threadName=="thread1"){val=10000;g_index=&val;sleep(5);}
main函數中:cout<<"這是thread2的val值: "<<*g_index<<endl;

?

線程的變量:__thread選項?

int *g_index;
//int g_val;
__thread int g_val;void *routine(void *args)
{int val = 0;threadData *data = (threadData *)args;for (int i = 1; i <=3 ; i++){//printf("%s tid: %p val: %d\n", data->threadName.c_str(), pthread_self(), val);//val++;printf("%s g_val: %d\n",data->threadName.c_str(),g_val);g_val++;}// if(data->threadName=="thread1")// {//     val=10000;//     g_index=&val;//     sleep(5);// }return (void *)0;
}int main()
{vector<pthread_t> tids;for (int i = 0; i < 3; i++){pthread_t tid;threadData *td = new threadData(i);pthread_create(&tid, nullptr, routine, td);tids.push_back(tid);sleep(1);}//cout<<"這是thread2的val值: "<<*g_index<<endl;for(auto t:tids){void *retData;pthread_join(t,&retData);}return 0;
}

我們線程在使用 g_val全局變量時:

g_val帶上__thread編譯選項時:

?

__thread是編譯選擇,不是c,c++的語法是編譯器的選項;

特點:

?1.將進程全局數據變為線程全局數據

2.只能給內置類型帶上這個選項

C++線程庫說明

在我們的C++中是有語言級別的線程庫的(C語言沒有),C++中的線程庫是跨平臺的,但是我們在使用C++線程庫時,我們還是會發現,我們需要帶上編譯選項-lpthread所以說明C++的線程庫是封裝了原生線程庫的,而原生線程庫在linux中是posix標準的,在windows中又有不同的標準;但是C++的線程庫是跨平臺的,所以說明C++的線程庫不僅封裝了linux的posix標準線程庫還封裝了windows下的線程庫;

clone系統調用的封裝

我們前面說線程是輕量級的進程,為什么這么說呢?其實我們在創建線程時使用的pthread_create函數和創建子進程的fork函數都是封裝了clone的系統調用;

int clone(int (*fn)(void *), void *child_stack
, int flags, void *arg
, ... /* pid_t *ptid, void *tls, pid_t *ctid */);

這個系統系統調用會指定一片棧空間給新開辟的線程,我們不需要懂clone調用的細節,我們只需要知道,linux中其實在底層上線程的接口也是和進程用的一樣的調用,所以它們在內核層面上是處于同一級別的執行流的,所以線程被稱為輕量級進程;

小提示:

線程如何使用進程替換的調用會將當前的整個進程替換掉

線程終止

前面我們說了線程的3個正常退出方式;我們下面來詳細的講解一下:

pthread_exit

這個函數就是和return一樣的作用,返回一個retval給主線程;這里需要注意的是retval最好是堆上的指針,線程終止棧幀也會銷毀,會導致棧上的數據被釋放,所以返回值一定要是不被釋放的數據;

pthread_cancel

這是一個線程終止函數,我們可以通過此函數終止掉tid的線程:

這里終止了就不需要再join了,如果join了會發返回非0值;?

這是gpt給出的提示:

盡管?pthread_cancel?函數可以請求取消另一個線程,但是線程是否真正被取消,以及何時被取消,是由目標線程自身來決定的。目標線程可以選擇忽略取消請求,或者在適當的時機響應取消請求并執行清理操作。

?線程分離

pthread_detach

我們的主線程永遠是最后退出的,因為需要等待所有創建進程退出,我們常見的服務器一般都是死循環不退出的程序;而當主線程不關系創建的線程的結果時,可以使用detach來斷開創建線程與主線程之間的關系;,主線程就不需要等待子線程了;

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include<cstring>
using namespace std;void* routine(void*args)
{cout<<"我是被創建線程"<<endl;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,routine,nullptr);void* ret;pthread_detach(tid);int ret_join=pthread_join(tid,&ret);printf("%s\n",strerror(ret_join));return 0;
}

當沒有detach時:

當創建的線程被detach時

?所以說明線程不能被同時detach和join;

此外線程可以自己detach自己;

以上就是線程的控制與基本概念,線程部分未完待續;?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/17656.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/17656.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/17656.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

“揭秘:為什么羊駝Ollama成為計算機運行大型語言模型的最佳拍檔?“

最近&#xff0c;AIM 評測了在計算機上本地運行大語言模型&#xff08;LLM&#xff09;的最佳工具&#xff0c;Ollama 脫穎而出&#xff0c;成為最高效的解決方案&#xff0c;提供了無與倫比的靈活性。Ollama 是 Jeffrey Morgan 開發的一款開源工具&#xff0c;它正在徹底改變愛…

我被恐嚇了,對方揚言要壓測我的網站

大家好我是聰&#xff0c;昨天真是水逆&#xff0c;在技術群里交流問題&#xff0c;竟然被人身攻擊了&#xff01;罵的話太難聽具體就不加討論了&#xff0c;人身攻擊我可以接受&#xff0c;我接受不了他竟然說要刷我接口&#xff01;&#xff01;&#xff01;&#xff01;這下…

啟用標準大頁后內存使用率下降了

未啟用前 [rootol819c ~]# free -htotal used free shared buff/cache available Mem: 9.3Gi 984Mi 379Mi 1.5Gi 8.0Gi 6.8Gi Swap: 15Gi 3.0Mi 15Gi [rootol819c ~]# free -htotal …

【NumPy】掌握NumPy的histogram函數:數據直方圖的生成與應用詳解

&#x1f9d1; 博主簡介&#xff1a;阿里巴巴嵌入式技術專家&#xff0c;深耕嵌入式人工智能領域&#xff0c;具備多年的嵌入式硬件產品研發管理經驗。 &#x1f4d2; 博客介紹&#xff1a;分享嵌入式開發領域的相關知識、經驗、思考和感悟&#xff0c;歡迎關注。提供嵌入式方向…

速度百倍提升,高性能 Python 編譯器 Codon 火了

引言 在當下的編程世界里&#xff0c;Python由于其易用性和強大的庫支持在數據科學、人工智能和網頁開發等多個領域占據著舉足輕重的地位。然而&#xff0c;Python的執行速度往往成為開發者的一大痛點。 針對 這一問題&#xff0c;Codon項目正試圖提供一個高效的解決方案。Codo…

java新特性(Stream API)

什么是 Stream API? Stream API 是 Java 8 引入的一個用于對集合數據進行函數式編程操作的強大的庫。它允許我們以一種更簡潔、易讀、高效的方式來處理集合數據,可以極大提高 Java 程序員的生產力,是目前為止對 Java 類庫最好的補充。 Stream API 的核心思想是將數據處理操作…

小識MFC,一套設計優雅與不優雅并存的類庫----小話MFC(2)

Q1&#xff1a; CPoint繼承于POINT&#xff0c;這樣有什么好處&#xff1f; A&#xff1a; 繼承的一個最基本的好處當然就是減少代碼量。CPoint和POINT內部數據一樣&#xff0c;只是一個提供了更多的方法來操作對象。 typedef struct tagPOINT {LONG x;LONG y; } POINT, *P…

【Flutter】動畫介紹隱式動畫

&#x1f525; 本文由 程序喵正在路上 原創&#xff0c;CSDN首發&#xff01; &#x1f496; 系列專欄&#xff1a;Flutter學習 &#x1f320; 首發時間&#xff1a;2024年5月28日 &#x1f98b; 歡迎關注&#x1f5b1;點贊&#x1f44d;收藏&#x1f31f;留言&#x1f43e; 目…

SpringMvc-restful設計風格

Restful 1、入門1.1 簡介1.2 實例 1、入門 1.1 簡介 RESTFul是什么 RESTFul是WEB服務接口的一種設計風格。 RESTFul定義了一組約束條件和規范&#xff0c;可以讓WEB服務接口更加簡潔、易于理解、易于擴展、安全可靠。 1.2 實例 web.xml <?xml version"1.0"…

5、xss-labs之level6

一、level6-----大小寫繞過 1、測試分析 測試了之前用過的payload&#xff0c;發現都不行&#xff0c;并且level4使用的Java偽協議也不行&#xff0c;可以得出<>、script、onclick都被過濾 2、構造payload 因為href被過濾&#xff0c;可以試一下大寫HREF 初試payload…

沒人愿意和我們最好的工程師一起工作

幾年前&#xff0c;有一位技術非常好的工程師&#xff08;我們叫他“喬恩”&#xff09;為我工作。 他的代碼寫得很好&#xff0c;代碼審查&#xff08;PRs&#xff09;也完成得很快。從技術角度來看&#xff0c;他是個出色的工程師。 但是我們從其他工程師那里得到了一些關于…

Python實現解碼二進制數據以匹配給定的C++結構體

要在Python中實現解碼二進制數據以匹配給定的C結構體Ytest&#xff0c;你需要了解每個字段在結構體中的偏移量&#xff08;由于結構體內存對齊&#xff0c;這些偏移量可能與字段的順序和大小不完全對應&#xff09;。不過&#xff0c;在沒有指定內存對齊的情況下&#xff0c;我…

使用nvm管理node多版本(安裝、卸載nvm,配置環境變量,更換npm淘寶鏡像)淘寶的鏡像域名更換

最近 使用nvm 管理 node 的時候發現nvm install node版本號 總是失敗。 nvm install 20.12.2Error retrieving "http://npm.taobao.org/mirrors/node/latest/SHASUMS256.txt": HTTP Status 404查看原因&#xff0c;因為淘寶的鏡像域名更換&#xff0c;由于 npm.taob…

2020職稱繼續教育--發揮好“顯著優勢”,堅持和完善生態文明制度體系,促進人與自然和諧共生

單選題&#xff08;共7題&#xff0c;每題5分&#xff09; 1、我國生態脆弱區廣布&#xff0c;適宜生存的空間不足&#xff08;&#xff09;。 B、三分之一 2、按照傳統文化的說法&#xff0c;制度體系與治理體系&#xff0c;是“體”和“用”的關系&#xff0c;強調&#xff…

BI系統:數據驅動的決策利器,引領企業走向智能化

在當今這個數據驅動的時代&#xff0c;商業智能&#xff08;BI&#xff09;系統已成為企業不可或缺的工具。BI系統不僅提高了數據處理和分析的效率&#xff0c;更重要的是&#xff0c;它能夠幫助企業做出更加明智和精準的決策。在數聚多年的從業經驗來看&#xff0c;BI系統的重…

基于直接二元搜索的片上偏振分束器設計 (Nature Photonics, 9, 6, (2015))案例復現

時間—2024.6.08 騰訊會議 智能算法驅動的光子學設計與應用

Dream

好像很多人夢寐以求的都是別人已經擁有的&#xff0c;多少人奮斗一生的目標&#xff0c;卻只是別人的起點&#xff0c;人生而自由&#xff0c;只是不在枷鎖之中&#xff0c;生活中沒有人不遺憾&#xff0c;只是沒有人喊疼&#xff0c;時間不會重來&#xff0c;已經過去了就讓它…

內存泄露問題? 怎么解決

內存泄漏在Android應用開發中是一個常見問題&#xff0c;它發生在對象不再被使用時&#xff0c;但仍然被引用&#xff0c;導致垃圾回收器無法釋放其占用的內存。這會逐漸消耗應用可用內存&#xff0c;最終可能導致應用運行緩慢、崩潰或被系統終止。以下是一些常見的內存泄漏場景…

vue3 使用vant

使用前提&#xff1a; vite創建的vue3項目 vanthttps://vant-ui.github.io/vant/#/zh-CN/home npm i vant 引入樣式&#xff1a; main.js import vant/lib/index.css vant封裝 import { showLoadingToast,closeToast,showDialog,showConfirmDialog } from vant;export func…

Typora圖床配置優化(PicGo-Core(command line) 插件 + gitee)

Typora圖床配置優化&#xff08;PicGo-Core(command line) 插件 gitee&#xff09; 前言 在日常使用Typora編寫markdown筆記時&#xff0c;經常需要插入圖片來幫助理解和整理邏輯。然而&#xff0c;由于圖片保存在本地&#xff0c;上傳到網上時經常出現圖片不見或錯誤警告的…