【LINUX操作系統】線程操作

了解了線程的基本原理之后,我們來學習線程在C語言官方庫中的寫法與用法。

1. 常見pthread接口及其背后邏輯

1.1 pthread_create

與線程有關的函數構成了?個完整的系列,絕?多數函數的名字都是以“pthread_”打頭的
?
要使?這些函數庫, 要通過引?頭文件?<pthread.h>
?
鏈接這些線程函數庫時要使?編譯器命令的“-l pthread”選項
???pthread_create 函數并不是Linux提供的創建線程的系統調用,而是Linux中對輕量級進程已有的接口進行封裝的動態庫中的函數
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void *routine(void *arg)
{//值得注意的是,線程執行的函數的返回值一定是一個void*,用于返回一個指針-》這樣的目的是讓所有類型的參數都能被返回//包括但不限于類對象,函數,容器等等。//而參數void*,是一種泛型編程的實現方式,這樣一來可以根據傳入的內容來指定完成任務。while (true){cout << "new thread" << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, run, (void *)"thread-1");while (true){cout << "main thread" << endl;sleep(1);}return 0;
}

?由實驗結果可知,main和new thread的執行順序并不固定。

?????????兩個線程是異步打印(使用)到同一個文件資源(顯示器)上,所以存在打印錯亂的情況,但是的確可以做到每一個線程完成自己的任務而不會受到其他線程的干擾,這兩個線程就是兩個不同的執行流,而因為routine函數始終在訪問同一個資源,所以此時其是不可重入函數。

pthread_create的底層其實是clone-------一個非常復雜不便于使用的函數。

由于Linux在內核代碼中并沒有將進程和線程完全分成兩個系統管理,所以clone的flag參數就有以下兩種選擇,創建一個parent(進程),或者是只創建一個輕量級進程(clone_thread)

創建進程還是創建線程?一個文字游戲而已

了解:clone第一個參數,即傳給該線程的函數,第二個是棧空間(那么多虛擬內存的段,為什么要單獨傳一個這?),最后一個是線程要執行的函數的參數。。。。。。。

所以,現在就可以進一步理解用戶級線程

????????

pthread_create調用的是clone,clone其實也是被封裝的,底層還有如do_clone等函數。

最后,關于pthread_create的返回值:

????????pthreads函數出錯時不會設置全局變量errno(??部分其他POSIX函數會這樣做)。?是將錯 誤代碼通過返回值返回。沒有出錯返回0,出錯返回錯誤碼。

??????? ?對于pthreads函數的錯誤,建議通過返回值判定,因為讀取返回值要?讀取線程內的errno變量的開銷更小。(strerror和errno都是之前介紹進程的時候提及的概念)

1.2?pthread_self

pthread_self用于查看任意線程的線程ID

在上一文中,我們提到以下指令,用于查看:

????????ps -aL | head -1 && ps -aL | grep thread

或者

while :; do ps ajx |head -1 && ps ajx | grep test | grep -v grep;sleep 1;echo"------------------------------------------------";done

這個線程的tid值似乎與之前提到的lwp不太一樣呢???

請記住這個伏筆,會在之后的內容中重點聊。

如果覺得這個數字太長,我們可以:

string ToHex(pthread_t tid)
{char buffer[64];snprintf(buffer,sizeof(buffer),"0x%lx",tid);return buffer;
}

這個tid似乎看著很像地址?????????

多線程狀態對于資源的使用

能夠使用以上兩個接口,我們便能創建一些多線程的demo。

下面我們來驗證一下多線程的棧區是否是獨立的:

????????

//.....................
void *run(void *arg)
{//值得注意的是,線程執行的函數的返回值一定是一個void*,用于返回一個指針-》這樣的目的是讓所有類型的參數都能被返回//包括但不限于類對象,函數,容器等等。//而參數void*,是一種泛型編程的實現方式,這樣一來可以根據傳入的內容來指定完成任務。string name = static_cast<const char*>(arg);int val = 0;while (true){cout << "new thread,name : "<<name << " my_threadID : "<< ToHex(pthread_self())<<"my val : "<<val<<endl;val++;if(name=="thread-1") val++;sleep(1);}
}int main()
{pthread_t tid1;pthread_t tid2;pthread_create(&tid1, nullptr, run, (void *)"thread-1");pthread_create(&tid2, nullptr, run, (void *)"thread-2");
//...........

實驗現象也不出所料:兩個棧的val值互不影響。

兩個新線程都在修改val,但是二者并沒有影響到另外的val,這也驗證了每個線程實際上也有自己的棧。

再嘗試讓兩個線程都修改同一個全局變量:

果然,兩個線程在某種意義上已經形成了“通信”。

????????將val變量作為了全局變量供兩個線程進行訪問,此時兩個線程均看得到這個變量,所以這個變量就是兩個線程的共享資源,與前面顯示器打印問題一樣,因為共用并且沒加保護,導致一個線程的修改會影響到另一個線程,這個效果同樣適用于靜態變量

兩個線程誰先執行不一定,并且不同的線程會瓜分時間片

其實,獨立的線程不代表是私有的線程,強行使用地址去訪問某一個線程棧的內容依然能訪問到。?

結果:

? ? ? ? ? ? ? ? ? ? ??

但是這樣的做法是非常不推薦的,是錯誤的。也在一定的程度上的說明,只要拿到虛擬地址就能訪問,一切的“不允許”都是被規則規定的。

?

線程局部存儲?

如果希望一個變量被每一個線程都單獨創建副本,則使用__thread來修飾該變量。該關鍵詞只能用于修飾內置類型。

棧的特性與存儲

棧是每個線程私有的內存區域,用于存儲局部變量、方法參數和返回地址等。每個新線程都會創建自己的棧。棧上的數據獨立于其他線程,這意味著如果一個線程在其棧中創建了一個變量,其他線程不能直接訪問該變量,除非通過方法參數或共享對象顯式傳遞。這種獨立性提供了天然的線程安全,因為每個線程都有自己的棧空間,不會無意間影響其他線程的數據。

堆的特性與存儲

堆是一個全局共享的內存區域,用于動態分配內存。所有線程都可以訪問堆上分配的對象。如果一個對象在堆上被創建,并且其引用(內存地址)被傳遞給多個線程,那么這些線程就可以同時訪問甚至修改該對象的內容。由于堆是共享的,所以在多線程環境下訪問堆上的對象時需要特別小心,通常需要使用同步機制來避免競態條件或數據不一致的問題。

總之,不論是堆上的數據還是棧上的數據,只要能獲取到指定內容的地址,就不存在所謂的棧獨立性,此刻棧和堆都是共享的,而所謂的棧獨立性和堆共享性更強調所有的線程都有自己的棧,但是堆共有一個


1.3 pthread_join

????????已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
???????創建新的線程不會也不能復?剛才退出線程的地址空間。
? ? ? ? 為了解決類似于進程中“僵尸進程”的問題,并且獲得線程的任務執行情況。被主線程創建的線程一般需要被等待并且回收。

參數 :
thread: 線程 ID(希望被join掉,也就是希望被終止的線程ID)
value_ptr: 它指向?個指針(雙重指針),是線程執行的函數的返回值的地址。
返回值:成功返回 0 ;失敗返回錯誤碼。

?比如我們現在不需要線程執行的任務的返回值:

pthread_join的第二個參數是一個輸出型參數,因為線程執行的函數是一個void*類型的返回值,為了讓這樣的void*參數能夠自動適應所有的返回值,所以必須使用一個void**的參數

?線程執行的任務想返回一個10,所以就用void*強轉一下10(ret是一個void*,用于接受這個10的值,所以10必須強轉成void*)。

?void*是8個字節(現在的linux機器或者其他機器幾乎都是64位),此時10被當成了一個void*

???????????????????????????????????????????????????????

所以用int去強轉會出現報錯,應該采用long long去強轉。

或者用一個堆指針來完成這個任務也可以,先把void*的指針變成int*,再解引用。

一個線程只要return,就退出了

一個void**類型的變量ret去記錄線程執行的函數的返回值(return的值)

錯誤使用:

這么做雖然能正確找到這個ret值,但是是不科學的,因為a是一個棧上的變量,按理來說這個變量會銷毀。更建議使用堆指針

以上代碼說明說明堆空間是共享的。線程之間能拿到其他線程創建的堆資源,不過后續需要自己銷毀資源

另外,直接pthread_join自己會報錯:

? ??

既然pthread_create的回調函數支持傳入任何參數,那么假設我們希望給每一個線程傳一個結構體,并且打印、操作該結構體對應的數據。

一個簡單的傳結構體指針demo:

class ThreadData
{
public:ThreadData(const string &name, int a, int b): _name(name), _dataA(a), _dataB(b){}pair<int, int> GetInfo(){return pair<int, int>(_dataA, _dataB);}const int GetA(){return _dataA;}void Exec(){_dataA++;_dataB *= 10;cout << _name << endl;}~ThreadData() {}private:string _name;int _dataA;int _dataB;
};void *routine(void *th_data)
{ThreadData *p_data = (ThreadData *)th_data;//while (true)//{cout << "new thread,myID: " << ToHex(pthread_self()) << " A:B " <<p_data->GetInfo().first << " : " << p_data->GetInfo().second << endl;p_data->Exec();cout << "new thread,myID: " << ToHex(pthread_self()) << " A:B " << p_data->GetInfo().first << " : " << p_data->GetInfo().second << endl;sleep(1);//}return (void*)p_data;
}int main()
{pthread_t tid;ThreadData *th_p = new ThreadData("thread-1", 10, 20);int err_n1 = pthread_create(&tid,nullptr,routine,(void*)th_p);if(err_n1!=0){cerr<<"create error "<<strerror(err_n1)<<endl;return 1;}else {cout<<"create success"<<endl;}void* ret = nullptr;int err_n2 = pthread_join(tid,&ret);if (err_n2 != 0){cerr << "join error" << strerror(err_n2) << endl;}elsecout << "joined success! " << "ret num A : " << ((ThreadData*)ret)->GetA() << endl;
}

創建多個線程的demo:

之前的ThreaData就是為了在一個新的線程的時候有對應的數據和方法? ?????????? ?

??pthread_join在進行的其實也是一種阻塞等待,這會影響主線程的工作效率。


1.4 線程分離pthread_detach

????????默認情況下,新創建的線程是joinable的,線程退出后,需要對其進?pthread_join操作,否則?法釋放資源,從?造成系統泄漏。
?
????????如果不關?線程的返回值,join是?種負擔,這個時候,我們可以告訴系統,當線程退出時,?動釋放線程資源

join和detach是矛盾的。

????????需要注意的是,在多執行流的情況下,一定要確保主執行流最后退出,防止主執行流先退出導致?分離的線程?需要的資源被釋放導致錯誤。線程可以自主分離(在線程的執行函數中執行pthread_detach(pthread_self()); 或者被迫分離-》主線程等其他線程調用該函數。

線程程序替換

????????注意,進程程序替換是不可以直接發生在線程執行流中的,因為程序替換的本質是替換掉當前進程的代碼,此時線程的代碼也會被替換從而導致錯誤,但是可以在線程中創建子進程,再在子進程中使用進程程序替換

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????

1.5 線程退出

首先,不能直接用exit(),因為exit()會直接讓整個進程都退出。

pthread_exit(),pthread_exit的參數也會被pthread_join給拿到----------->因為pthread_join本來就是拿的return的值,現在由pthread_exit完成這個“return”的任務。

pthread_cancel

pthread_cancel

:主要用于主線程去取消其他進程。(類似于父進程去取消子進程的過程)

但是不建議直接使用,因為不清楚被取消的線程的工作狀態

一個被pthread_cancle取消的線程的join值是-1(ret 返回的值)

這個接口要慎用,如果在新線程未完成任務就被主線程結束,主線程可能也會結束。

并且主線程在cancle的時候可能不清楚新線程的狀態,有可能出錯。?

一個被pthread_cancle取消的線程的join值是-1(ret? ? )


2. CPP的封裝

除了C語言對系統的相關線程操作進行了封裝外,其他編程語言也會做同樣的事情,這樣可以確保語言具有可移植性,同樣,C++中也有對應的線程庫<thread>,具體接口見C++線程庫部分,但是需要注意的是,如果是在Linux下使用C++線程庫編寫代碼,編譯該代碼時依舊需要攜帶-lpthread,因為C++線程庫本質也是封裝了libpthread.so

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 錯誤:? ?

lambda表達式構造thread:

? ?

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

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

相關文章

【AI面試準備】Azure DevOps沙箱實驗全流程詳解

介紹動手實驗&#xff1a;通過 Azure DevOps 沙箱環境實操&#xff0c;體驗從代碼提交到測試篩選的全流程。如何快速掌握&#xff0c;以及在實際工作中如何運用。 通過 Azure DevOps 沙箱環境進行動手實驗&#xff0c;是快速掌握 DevOps 全流程&#xff08;從代碼提交到測試篩選…

VulnHub-DC-2靶機

主機發現 sudo arp-scan -l 以sudo管理員權限掃描本地活動ip地址 Interface: eth0, type: EN10MB, MAC: 08:00:27:22:46:4f, IPv4: 192.168.252.230 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.252.6 4c:5f:70:74:3c:3b …

藏語英語中文機器翻譯入門實踐

&#x1f3af; 項目目標&#xff1a; 輸入藏文句子&#xff0c;自動翻譯成英文和中文&#xff08;或輸入中文&#xff0c;翻譯為英文和藏文&#xff09;。 &#x1f50d; 技術與原理簡介 機器翻譯&#xff08;Machine Translation, MT&#xff09;是人工智能中自然語言處理&a…

【阿里云大模型高級工程師ACP習題集】2.9 大模型應用生產實踐(上篇)

練習題 【單選題】在自然語言處理的法務咨詢場景中,以下哪種模型選擇最為合適? A. 通用大語言模型 B. 經過數學領域微調的模型 C. 面向法律領域訓練的模型 D. 視覺模型 【多選題】以下哪些屬于模型非功能性需求?( ) A. 模型對不同語言的支持能力 B. 模型的響應速度要求 C.…

WPF之ProgressBar控件詳解

文章目錄 1. ProgressBar控件簡介2. ProgressBar的基本屬性和用法2.1 基本屬性2.2 基本用法2.3 代碼中修改進度 3. 確定與不確定模式3.1 確定模式&#xff08;Determinate&#xff09;3.2 不確定模式&#xff08;Indeterminate&#xff09; 4. 在多線程環境中更新ProgressBar4.…

IntelliJ IDEA 保姆級安裝教程(附安裝包)

文章目錄 一、下載二、安裝三、啟動 一、下載 Ultimate 2021.1.1 - Windows x64 (exe) 二、安裝 三、啟動 首次安裝啟動 非首次安裝啟動

Performance API 性能上報

以下是關于 Performance API 性能上報的基本知識點總結: 一、性能監控核心指標體系 1. 關鍵性能指標(Web Vitals) 指標標準采集方式健康閾值LCP (最大內容繪制)測量加載性能PerformanceObserver≤2.5sFID (首次輸入延遲)測量交互響應PerformanceObserver≤100msCLS (累積布…

C語言-指針(一)

目錄 指針 內存 概念 指針變量 取地址操作符&#xff08;&&#xff09; 操作符“ * ” 指針變量的大小 注意 指針類型的意義 作用 void * 指針 const修飾指針變量 const放在*前 const放在*后 雙重const修飾 指針的運算 1.指針 - 整數 2.指針 - 指針 3.指…

華為云Astro大屏連接器創建操作實例:抽取物聯網iotda影子設備數據的連接器創建

目錄 樣圖(API連接器創建成功) 說明 操作場景(以Astro大屏抽取iotda影子參數為例) 實際操作步驟 新建連接器 設置基本信息。 接口鑒權方式,支持API鑒權、AK/SK、API Key和無身份驗證 無身份驗證 AK/SK認證(目前暫不能用) API Key認證(第三方使用) API鑒權認…

【硬件系統架構】哈佛架構

一、引言 在計算機科學的浩瀚宇宙中&#xff0c;計算機體系結構猶如星辰般繁多且各有獨特光芒。哈佛架構便是其中一顆耀眼的明星&#xff0c;它在眾多計算機體系結構中占據著獨特而重要的地位。從計算機技術的萌芽期一路走來&#xff0c;哈佛架構不斷發展演變&#xff0c;在不同…

華為eNSP:IS-IS認證

一、什么是IS-IS認證&#xff1f; 華為eNSP中的IS-IS認證 IS-IS認證是華為eNSP網絡中用于保障中間系統到中間系統&#xff08;IS-IS&#xff09;協議通信安全性的核心機制&#xff0c;通過身份驗證和數據完整性校驗防止非法路由信息注入或篡改。其實現方式與關鍵特性如下&…

如何創建并使用極狐GitLab 項目訪問令牌?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 項目訪問令牌 在極狐GitLab 16.1中引入添加了默認前綴。 項目訪問令牌類似于密碼&#xff0c;但你可以 限制訪問資源&#xf…

C# 異步詳解

C# 異步編程詳解 一、異步編程基礎概念 1. 同步 vs 異步 ??同步(Synchronous)??&#xff1a;任務按順序執行&#xff0c;前一個任務完成后才會執行下一個??異步(Asynchronous)??&#xff1a;任務可以非阻塞地啟動&#xff0c;主線程可以繼續執行其他操作 2. 異步編…

C++ 之 【模擬實現 list(節點、迭代器、常見接口)】(將三個模板放在同一個命名空間就實現 list 啦)

1.前提準備 (1) list 的底層結構一般是帶頭雙向循環鏈表 (1)為避免命名沖突&#xff0c;需要創建一個命名空間來存放模擬實現的 list (2)下面模擬實現list時&#xff0c;聲明和定義不分離(具體原因后續講解) 2.完整實現 2.1 鏈表節點 template<class T>//節點寫成類模板…

解決Win10虛擬機“網絡連接不上”,“Ethernet0 網絡電纜被拔出”的問題

一、情景引入 今天用Win10虛擬機打開瀏覽器發現&#xff1a; 很奇怪&#xff0c;平常都沒有這個問題。 二、檢查網絡狀態 點擊更改適配器選項&#xff0c;發現如下&#xff1a; 三、解決問題 打開任務管理器&#xff0c;點擊服務&#xff0c;搜索欄搜索&#xff1a;VM …

2025藍橋杯省賽網絡安全組wp

文章目錄 黑客密室逃脫ezEvtxflowzipEnigma星際xml解析器EBC-TrainAES-CBC 黑客密室逃脫 提示猜文件名&#xff0c;猜幾個常見的&#xff0c;app.py讀到源碼 這里也是腦抽了一下&#xff0c;把密鑰看成1236了。。。卡了五分鐘左右&#xff0c;解出來的時候已經降到300多分了&a…

算法查找目錄

1. 基礎數據結構 數組與鏈表 動態數組 實現與自動擴容機制均攤分析ArrayList/Vector實現 單向鏈表 基本操作(插入、刪除、查找)鏈表反轉環檢測(Floyd判圈算法) 雙向鏈表 插入刪除操作優化雙向遍歷優勢邊界情況處理 循環鏈表 約瑟夫環問題單向循環鏈表雙向循環鏈表 跳表 基本原…

Windows11 管理員用戶下無權限操作的解決方法

問題 Program Files 目錄下無權限進行寫入操作。 Windows 各種功能因權限不足無法訪問。 啟動某些應用程序時&#xff0c;可能會遇到 用戶賬戶控制 (UAC, User Account Control) 彈窗提示&#xff0c;要求確認是否允許該程序對設備進行更改。 等等問題 解決方法 運行 p…

.NET中,const和readonly區別

在.NET中&#xff0c;const和readonly都用于定義不可變的值&#xff0c;但它們在行為和使用場景上有顯著區別。以下是兩者的詳細對比&#xff1a; 初始化時機 ? const ? 編譯時常量&#xff0c;必須在聲明時賦值。 ? 值在編譯時確定&#xff0c;并被直接嵌入到IL代碼中&…

郵件分類特征維度實驗分析

活動發起人小虛竹 想對你說&#xff1a; 這是一個以寫作博客為目的的創作活動&#xff0c;旨在鼓勵大學生博主們挖掘自己的創作潛能&#xff0c;展現自己的寫作才華。如果你是一位熱愛寫作的、想要展現自己創作才華的小伙伴&#xff0c;那么&#xff0c;快來參加吧&#xff01…