【Linux】線程概念與控制

一. 線程的概念

1.什么是線程

線程是進程內部的一個執行流,是進程調度的基本單位。它具有輕量的特點,它的創建和銷毀所消耗的資源更少,線程間切換比進程間切換消耗的資源更少;它與進程共享一張虛擬地址空間表,通過進程來給線程執行流分配資源;同時,每個線程都是獨立執行的,擁有自己的程序計數器和上下文切換。

簡單來說在Linux下,一個進程由多個 task_struct ,一張虛擬地址空間表和頁表構成。而線程就是一個 task_struct ,進程內部的一個執行流,所有的線程都指向同一張虛擬地址空間表,讓進程共同管理。這樣我們就可以對所有線程的資源進行劃分,劃分為堆區棧區共享區等等。通過一張頁表映射到物理內存當中。

總的來說,線程就是一個 task_struct ,通過共同指向同一張虛擬地址空間的方式實現了共同管理,降低了創建調度銷毀成本。

2.深刻理解虛擬地址空間

在虛擬地址空間中,頁表用于映射虛擬地址空間到實際的物理內存。我們在管理虛擬地址空間的時候,它的地址是連續的,而物理地址空間則是可以分散碎片的。在虛擬地址空間中,我們存儲同一個資源的時候地址空間需要連續,但在物理地址當中,我們會將同類型的資源盡可能放到一處(這樣可以節省空間),無論是哪個線程都可以將數據進行整合。

有了虛擬地址空間和物理地址空間,那么我們如何將他們連接起來呢?再加一層頁表就好。

在 Linux 當中,頁表是由,三級頁表組成一級頁表是頁表目錄,其中存儲著各個頁表的地址;二級目錄是各個頁表,頁表指向各個頁幀的地址(4 KB);三級頁表就是頁幀,每個頁幀由 4 KB 構成。在虛擬地址空間中,每個數據在虛擬地址空間下都有一個 32 字節的地址,這 32 個字節需要分為 10 + 10 + 12 來進行閱讀,首先定位到頁表目錄當中,前10個字節,用于在頁表目錄當中找到對應的頁表;中間的10個字節用于在當前頁表當中找到對應的頁幀;最后的12個字節用于對頁幀的起始位置的偏移量,這樣我們就能通過虛擬地址找到相對于的物理地址

下面是一個簡化圖

有了頁幀,該如何管理呢? 先描述再組織,操作系統引入了 struct page 結構。對于每個頁幀,都有一個 struct page 對它進行相應的管理。

下面我來介紹一下 struct page 的結構構成。

該結構主要用于管理記錄,跟蹤頁幀的使用狀態,頁針的歸屬,管理頁幀的映射關系,回收頁幀等等。

首先是狀態標識(flags),用于記錄頁幀的基本狀態,是被鎖定被修改還是內核保留;引用計數(_refcount)記錄該頁幀被引用的次數;映射關系(mapping + index)mapping 指向該文件的存儲頁,index 用于指向在該頁下的偏移量。


總結:

1. 虛擬地址和物理地址管理,通過頁表進行映射,使得其完成了解耦的操作。

2. 頁表按需創建和分頁機制有效的節省了空間消耗。

3.線程的優缺點

(1)優點

線程的創建相比于進程的創建代價要小很多且占用資源少,線程只需要創建 task_struct 掛接到虛擬地址空間上即可,而進程的創建就要涉及虛擬地址空間頁表等等資源;線程切換比進程切換效率高,如果要進行進程間的切換,就需要連同虛擬地址空間等進行統一切換,而線程只需要切換 task_struct 和上下文資源即可;線程可以利用多處理器進行并發運行,提高 IO 效率和計算效率;

(2)缺點

線程共享進程的地址空間,因此可以訪問到當前的共享資源,這就導致,若缺乏同步機制,線程會引發數據競爭,導致程序異常;當線程過多時,容易導致資源限制,首先每個線程都有自己的獨立線程棧在內存當中,大小為 8 MB ,若線程過多就容易導致內存空間耗盡。其次,若線程過多,CPU的調度開銷也對應的增加,CPU 將時間花在了不斷調度線程中,導致 CPU 利用率下降。最后,每個線程創建都會在內核當中創建一個 TCB 資源,這也就導致了高頻創建銷毀會給內核增加負擔。進程擁有較高的獨立性,即使程序出錯進程崩潰,這也不會影響其他的進程運行,但如果線程崩潰,可能會導致整個進程都退出。

二. 線程的控制

1.線程創建

pthread_create:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);

參數說明:

thread:獲取創建成功的線程 ID ,該參數是一個輸出型參數。

attr:用于設置進程屬性,傳入NULL 表示使用默認值。

start_routine:返回值和參數均為 void* 的函數指針。該參數表示線程例程,即后續線程需要執行的函數。

arg:傳給線程實例的參數。

返回值:

成功返回0,失敗返回錯誤碼。

下面我們來看一個示例,讓一個主線程創建一個新線程

當一個程序啟動時,就有一個進程被操作系統創建,于此同時一個線程也立刻運行,這個線程就是主線程。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void *startRoutine(void* args)
{while(true){cout<<"線程正在運行"<<endl;sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid,nullptr,startRoutine,(void*)"thread-1");cout<<"new thread id:"<<tid<<endl;while(true){cout<<"main pthread 正在運行"<<endl;sleep(1);}return 0;
}

運行結果:


當我們想獲取線程 id 時,可以使用 pthread_self 函數,我們來看下面的代碼

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;static void Print(const char* name,pthread_t tid)
{cout<<name<<" 正在運行"<<tid<<endl;
}void *routine(void* argv)
{const char* name = static_cast<const char*>(argv);while(true){Print(name,pthread_self());sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid,nullptr,routine,(void*)"thread");while(true){cout<<"main thread run"<<endl;sleep(1);}return 0;
}

下面是運行結果:

在線程運行中,調用了 pthread_self 函數將當前線程的tid傳給了函數進行調用。


2.線程終止

終止一個線程有三種方法:

1.從線程函數 return

2.在線程中調用

3.在線程中調用 pthread_exit 終止其它進程中的另一個線程

方法一:(從線程return)

方法較簡單不詳細講解

方法二:(pthread_exit

pthread_exit 的功能就是終止線程

#include <pthread.h>
void pthread_exit(void* retval);

參數說明:

retval:線程退出碼

注意:

pthread_exit return 返回的指針所指向的內存單元必須是全局的或者是 malloc 分配的,若是在線程內部創建的指針返回會導致訪問結果不可控。因為隨著 pthread_exit 線程棧上存儲的數據也會被銷毀

下面看一下正確使用:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;static void printTid(const char* name,const pthread_t &tid)
{cout<<name<<" 正在運行 "<<tid<<"  "<<endl;
}void* Routine(void* argv)
{const char* name = static_cast<const char*>(argv);int cnt = 5;while(true){printTid(name,pthread_self());if(!(cnt--)){break;}sleep(1);}cout<<"線程退出"<<endl;pthread_exit((void*)11111);
}int main()
{pthread_t tid;int n = pthread_create(&tid,nullptr,Routine,(void*)"thread");void* ret = nullptr;pthread_join(tid,&ret);cout<<"main pthread success   "<<(long long)ret<<endl;sleep(5);while(true){printTid("main othread",pthread_self());sleep(2);}return 0;
}

運行結果:


3.線程等待

pthread_join:

類比于進程等待,線程創建也是需要被等待的,如果一個新線程被創建出來,主線程不進行等待,那么這個新線程的資源就無法被回收,就會導致資源泄露。在線程中等待的函數叫 pthread_join?

#include <pthread.h>
int pthread_join(pthread_t thread,void **retval);

參數說明:

thread:被等待的線程tid

retval:線程退出時的信息碼

返回值:

成功返回0,失敗返回信息碼

下面是代碼樣例:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void *thread_func(void *arg) 
{printf("子線程任務完成\n");pthread_exit((void*)100);  sleep(200);
}int main() 
{pthread_t tid;void *ret;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, &ret);  printf("子線程退出狀態:%ld\n", (long)ret);return 0;
}

運行結果:

主線程進行阻塞,等待子線程完成任務后,主線程才會繼續運行

4.線程分離

pthread_detach:

線程分離與線程等待是一對互斥關系,當我們主線程不需要關心子線程的返回值時,我們可以將子線程進行分離(也可以是子線程自行分離),分離后的線程會繼續執行自己的內容。一個線程被分離了,這個進程依舊需要管理這個線程的資源,若被分離的線程出現故障也有可能會影響其他的線程或者當前進程。分離的線程可以減輕 join 的負擔,意味著主線程不需要再關注子線程了,而子線程執行完畢后也會自行釋放資源。

#include <pthread.h>
int pthread_detach(pthread_t thread);

參數說明:

thread:被分離的線程 ID?

返回值:

成功返回0,失敗返回錯誤碼


5.POSIX線程庫

在Linux當中,站在內核角度實際上并沒有關于線程相關的接口,但是用戶希望創建線程時可以調用接口,這樣可以使編碼更加便捷。于是,便有了第三方的線程庫,基于這個第三方庫,它為用戶提供了線程相關的接口,構成了線程有關的完整系列。

這些接口大多數都是以 pthread_? 打頭,在使用前需要包含頭文件? <pthread.h> ,鏈接庫時需要包含? -lpthread 選項。

6.線程棧和 pthread_t

線程是一個獨立的執行流,在運行的過程中也會產生自己的數據,所以線程擁有自己的獨立的棧,線程棧會隨著線程的銷毀被回收。

在 Linux 中,基于線程的接口都是通過外部庫封裝后進行調用的,pthread_t 是線程的身份證,用于識別和操作線程。在外部庫中,pthread_t 是由?thread_info 結構體進行管理的。

struct thread_info
{pthread_t tid;void *stack;
}

與其一同管理的便是線程棧。每當用戶創建一個線程時,就會在動態庫中創建一個線程控制塊 thread_info ,給用戶返回一個 pthread_t ,也就是該結構體的起始虛擬地址。

主線程中的棧區使用的是地址空間中的棧區,而創建的子線程用的是庫中提供的棧結構。

7.線程的局部存儲

在線程中,全局變量是共享的,所有的線程可以共用一份全局變量,如果想讓全局變量私有那么可以進行線程變量的局部存儲

下面我們來驗證一下,線程可以共用同一份全局變量

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;int global_val = 100;void *Routine(void *argv)
{const char* name = static_cast<const char*>(argv);while(true){cout<<"thread: "<<name<<"  global_value"<<global_val<<" new: "<<global_val++<<"address: "<<&global_val<<endl;sleep(1);}
}int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(&tid1,nullptr,Routine,(void*)"thread1");pthread_create(&tid2,nullptr,Routine,(void*)"thread2");pthread_create(&tid3,nullptr,Routine,(void*)"thread3");pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
}

運行結果:

我們發現,全局變量在主線程和子線程下該變量的地址都是一致的,它們所用的是同一個變量

若我們希望在每一個子線程下都創建一份變量我們可以這樣操作

樣例代碼:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;__thread int global_val = 100;void *Routine(void *argv)
{const char* name = static_cast<const char*>(argv);while(true){cout<<"thread: "<<name<<"  global_value"<<global_val<<" new: "<<global_val++<<"address: "<<&global_val<<endl;sleep(1);}
}int main()
{pthread_t tid1;pthread_t tid2;pthread_t tid3;pthread_create(&tid1,nullptr,Routine,(void*)"thread1");pthread_create(&tid2,nullptr,Routine,(void*)"thread2");pthread_create(&tid3,nullptr,Routine,(void*)"thread3");pthread_join(tid1,nullptr);pthread_join(tid2,nullptr);pthread_join(tid3,nullptr);return 0;
}

運行結果:

我們只要在全局變量前加上??__thread? ,此時所有的線程都在自己的棧上拿到了一份數據,我們可以觀察到,此時全局變量打印出的地址是不同的,且變量是肚子增加的。


三. 線程的封裝

線程封裝

我們簡單的對線程進行封裝,使其能進行創建分離等待終止等功能

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <functional>
#include <pthread.h>
using namespace std;static uint32_t number = 1;template <typename T>
class Thread
{using func_t = function<void(T)>;private:void EnableDetach(){std::cout << "線程被分離了" << std::endl;_isdetach = true;}void EnableRunning(){_isrunning = true;}static void *Routine(void *argv){Thread<T> *self = static_cast<Thread<T> *>(argv);self->EnableRunning();if (self->_isdetach)self->Detach();self->_func(self->_data); // 回調處理return nullptr;}public:Thread(func_t func, T Data): _tid(0), _isrunning(false), _isdetach(false), _Data(Data), _func(func){_name = "Thread - " + to_string(_number++);}void Detach(){if (_isdetach)return;int n = pthread_detach(_tid);if (n != 0){cerr << "fail to detach" << strerror(n) << endl;}else{cout << "success to detach" << endl;_isdetach = true;}}void Join(){if (_isdetach){cout << "線程已經分離,無法進行等待" << endl;return;}int n = pthread_join(_tid, &res);if (n != 0){cerr << "fail to join" << strerror(n) << endl;}else{cout << "success to join" << endl;}}bool Start(){if (_isrunning)return false;int n = pthread_create(&tid, nullptr, Routine, this);if (n != 0){cerr << "fail to create pthread" << strerror(n) << endl;return false;}else{cout << "success to create pthread" << strerror(n) << endl;return true;}}bool Stop(){if (!_isrunning)return false;int n = pthread_cancel(tid);if (n != 0){cerr << "fail to Stop" << strerror(n) << endl;return false;}else{cout << "success to Stop" << endl;_isrunning = false;return true;}}~Thread(){}private:string _name;pthread_t _tid;bool _isrunning;bool _isdetach;T _Data;void *res;func_t _func;
};

感謝各位觀看,望多多支持!!!

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

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

相關文章

雙軸傾角傳感器廠家與物聯網角度傳感器應用全解析

本文主要探討雙軸傾角傳感器廠家的核心技術優勢&#xff0c;以及物聯網角度傳感器在智能監測中的創新應用。同時&#xff0c;也詳細介紹了水平監測傳感器廠家的解決方案特點&#xff0c;并分析了專業進口傾角傳感器代理所提供的原廠品質保障與本地化服務支持。以深圳瑞慣科技有…

容器-資源隔離機制

一. 引言&#xff1a; 大家都知道&#xff0c;在一臺機器上&#xff0c;可以運行任意(根據系統資源)個容器實例。且各容器間是相互獨立&#xff0c;不做任何關聯的。那么&#xff0c;docker是通過什么方式來實現容器隔離的呢&#xff1f; 接下來我們了解下。 二. 關于容器隔離…

Agentic RL Survey: 從被動生成到自主決策

Agentic RL Survey: 從被動生成到自主決策 本文將系統解讀《The Landscape of Agentic Reinforcement Learning for LLMs: A Survey》這篇綜述。該綜述首次將智能體強化學習&#xff08;Agentic RL&#xff09;與傳統LLM-RL范式正式區分&#xff0c;通過MDP/POMDP理論框架梳理…

徹底禁用 CentOS 7.9 中 vi/vim 的滴滴聲

在 VMware 虛擬機中安裝的 CentOS 7.9 系統&#xff0c;即使通過修改 /etc/inputrc 禁用了終端鈴聲&#xff08;set bell-style none&#xff09;&#xff0c;vi 或 vim 編輯時仍可能發出滴滴聲。這是因為 vi/vim 有自己獨立的鈴聲控制機制。以下是解決方法&#xff1a;方法 1&…

基于A2A和ADK的內容規劃代理

項目概述 Content Planner Agent 是一個基于 Google Agent Development Kit (ADK) 和 Python A2A SDK 構建的智能內容規劃代理。該代理能夠根據高層次的內容描述&#xff0c;創建詳細的內容大綱。 什么是A2A Protocol A2A Protocol&#xff08;Agent2Agent 協議&#xff09;…

Linux-條件變量

文章目錄條件變量概述條件變量的優缺點條件變量相關函數pthread_cond_init函數pthread_cond_destroy函數pthread_cond_wait函數pthread_cond_signal函數測試生產者和消費者模型條件變量 概述 與互斥鎖不同&#xff0c;條件變量是用來等待而不是用來上鎖的&#xff0c;條件變量…

[硬件電路-166]:Multisim - SPICE與Verilog語言的區別

SPICE與Verilog語言在電子設計領域中扮演不同角色&#xff0c;SPICE是電路仿真語言&#xff0c;用于精確模擬電路行為&#xff1b;Verilog是硬件描述語言&#xff0c;用于描述數字電路的結構和行為。以下是兩者的詳細區別&#xff1a;一、核心定位與用途SPICE&#xff1a;電路仿…

玩轉Docker | 使用Docker部署Umbrel操作系統

玩轉Docker | 使用Docker部署Umbrel操作系統 前言 一、 Umbrel 介紹 Umbrel簡介 Umbrel主要特點 二、系統要求 環境要求 環境檢查 Docker版本檢查 檢查操作系統版本 三、部署Umbrel服務 下載Umbrel鏡像 編輯部署文件 創建容器 檢查容器狀態 檢查服務端口 安全設置 四、訪問Umbr…

Flink Task線程處理模型:Mailbox

Task的線程 和 MailboxProcessor 的綁定executingThread 是 Task 類&#xff08;StreamTask 的父類&#xff09;在構造時創建的物理線程。MailboxProcessor 是 StreamTask 用來處理異步事件和驅動其主要處理邏輯&#xff08;processInput&#xff09;的核心組件。它們之間的綁定…

OpenCV 銀行卡號識別

目錄 一、項目原理與核心技術 二、環境準備與工具包導入 1. 環境依賴 2. 工具包導入 三、自定義工具類 myutils.py 實現 四、主程序核心流程&#xff08;銀行卡識別.py&#xff09; 1. 命令行參數設置 2. 銀行卡類型映射 3. 輔助函數&#xff1a;圖像展示 五、步驟 1…

計算機二級Python

一.靜態語言和腳本語言高級語言根據計算機執行機制的不同分為兩類&#xff1a;靜態語言和腳本語言靜態語言的核心特征&#xff1a;變量的類型在編譯時&#xff08;寫代碼時&#xff09;就必須確定并固定下來&#xff0c;即在使用一個變量前必須顯式地聲明它地類型一旦聲明&…

Mybatis Log Plugin打印日志,會導致CPU升高卡死

原因 大量日志輸出:MyBatis Log Plugin 會打印大量的 SQL 日志,包括 SQL 語句及其參數。如果項目中 SQL 查詢頻繁且復雜,日志量會非常大,導致 CPU 使用率升高,甚至卡死。 日志級別設置不當:如果將日志級別設置為 DEBUG 或 TRACE,MyBatis 會輸出非常詳細的日志信息,這會…

鴻蒙:深色模式適配和淺色模式的切換

前言&#xff1a; 有些時候我們需要對應用進行深色模式的適配處理&#xff0c;并且在不需要的時候切換到淺色狀態&#xff0c;下面和大家一起照著官方文檔來學習。 下面是官方文檔的鏈接&#xff1a; https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-dark-…

Coze源碼分析-資源庫-刪除插件-后端源碼-數據訪問和基礎設施層

5. 數據訪問層 5.1 倉儲接口定義 插件倉儲接口 文件位置&#xff1a;backend/domain/plugin/repository/plugin.go type PluginRepository interface {// DeleteDraftPlugin 刪除插件草稿DeleteDraftPlugin(ctx context.Context, pluginID int64) error// DeleteAPPAllPlugins …

案例一: 對基礎選擇器的使用【網頁盒子】

【1】樣例&#xff1a;首先&#xff0c;觀察到&#xff0c;幾個元素豎著排列的&#xff0c;所以使用塊級元素&#xff0c;而不是行內元素。【2】代碼演示<head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,…

爬蟲項目優化:如何用 Redis 實現 “斷點續爬”?避免重復采集電商數據

在電商數據采集場景中&#xff0c;爬蟲常因網絡波動、服務器重啟、IP 封禁等問題中斷。若缺乏斷點續爬機制&#xff0c;重啟后需從頭開始&#xff0c;不僅浪費帶寬與時間&#xff0c;還可能因重復采集導致數據冗余。Redis 憑借其高性能、原子操作、多樣數據結構的特性&#xff…

決策樹概念與原理

決策樹簡介決策樹是一種樹形結構樹中每個內部節點表示一個特征上的判斷&#xff0c;每個分支代表一個判斷結果的輸出&#xff0c;每個葉子節點代表一種分類結果(僅舉例無其他意義或隱喻)就像一個女孩去相親&#xff0c;那么首先詢問是否大于30&#xff0c;大于則不見&#xff0…

SQL面試題及詳細答案150道(116-135) --- 高級查詢與函數篇

《前后端面試題》專欄集合了前后端各個知識模塊的面試題,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。 前后端面試題-專欄總目錄 文章目錄 一、本文面試題目錄 116. 如何使用CASE語句實…

VeRL:強化學習與大模型訓練的高效融合框架

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 1 概述&#xff1a;VeRL的起源與核心價值 VeRL&#xff08;Versatile…

2. 計算機系統基礎知識

1 計算機系統概述 計算機系統 (Computer System) 是指用于數據管理的計算機硬件、軟件及網絡組成的系統。 計算機系統可劃分為硬件(子系統)和軟件(子系統)兩部分。硬件由機械、電子元器件、磁介質和光介質等物理實體構成&#xff0c;例如處理器(含運算單元和控制單元)、存儲器、…