linux 線程 進程 - 草尖舞者的個人空間 -_多線程

目錄

Linux線程控制

多線程角度理解資源"劃分"

可執行程序角度理解資源"劃分"

進程 vs 線程

線程背景

Linux多線程的實現 -- 內核角度

pthread庫

進程vs線程, 線程其他理論話題

面試題

線程優點

線程缺點

線程異常

線程用途

哪些資源共享,哪些獨佔

進程和線程

進程的多個線程共享

總結


Linux線程控制

為了方便理解資源劃分的本質,這裏直接通過編寫代碼從實踐再到理論.

多線程角度理解資源"劃分"

int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void *), void *restrict arg);
功能:創建一個新的的線程(注意:該函數是第三方庫,不屬於C/C++)

參數:

  • thread:返回線程ID(輸出型參數)
  • attr:設置線程的屬性(優先級,棧大小之類),attr為NULL表⽰使⽤默認屬性
  • start_routine:回調函數(函數指針類型),線程啓動後要執⾏的函數
  • arg:傳給線程啓動函數的參數

返回值:成功返回0;失敗返回錯誤碼

void *thread_routine(void *arg)
{
    std::string name = (const char *)(arg);
    // 第二個死循環
    while (true)
    {
        std::cout << "我是新線程...,名字: " << name << ", pid: " << getpid() << std::endl;
        sleep(1);
    }
}
int main()
{
    fun();
    pthread_t tid;
    pthread_create(&tid, NULL, thread_routine, (void *)"thread-1");
    while (true)
    {
        std::cout << "我是主線程..., pid: " << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

通過我們之前講過若是是使用第三方庫必須要指令庫名稱,而庫路徑是能夠被放到特定路徑下的.

g++ *.cc -pthread(指定庫名稱)

編譯執行該可執行程序後:

linux 線程 進程 - 草尖舞者的個人空間 -_虛擬地址_02

疑惑一:一個單進程代碼,可能讓兩個死循環跑起來??不可能!!這説明一個進程內部,現在已經有兩個不同的執行流!!!

疑惑二:主線程和新線程的pid是一樣的啊!和之前創建進程是不一樣的,使用ps ajx查看也確實存在一個進程.

使用ldd指令查看該可執行程序所依賴的動態庫:

linux 線程 進程 - 草尖舞者的個人空間 -_多線程_03

疑惑三:我們不是説依賴第三方庫嗎?可是我怎麼見到所謂的thread庫呢?這是由於我是ubuntu24.04的OS,thread被集成進libc裏了,而在ubuntu22.04確實能找到該庫.

可執行程序角度理解資源"劃分"

main函數和函數都有地址,一個函數所對應的地址難道就只有一個嗎?

thread -->一個可執行程序 -->一個進程呀於 -->有幾套頁表?-->1套頁表

反彙編觀察該可執行程序:objdump -S thread > test.s

linux 線程 進程 - 草尖舞者的個人空間 -_多線程_04

  1. 進程看到資源的”窗口!就是進程頁表的本質是什麼?如果沒有頁表,虛擬地址空間就沒有意義,也不能找到對應的物理內存,
  2. 所以,誰擁有更多的虛擬地址,誰就擁有更多的物理內存資源!
  3. 虛擬地址的集合!就是對函數進行編址,讓不同的執行流,執行不同的函數函數
  4. 讓不同的線程,執行不同的函數本質是讓不同的線程,依據擁有不同區域的虛擬地址擁有不同的資源!
  5. 通過函數編譯的方式,進行了進程內部的"資源劃分"

進程 vs 線程

  • 進程: 一個運行起來的執行流,一個加載到內存中的工具(教材),進程 = 內核數據結構 +自己的代碼和數據
  • 線程:進程內部的一個執行流,輕量化

如何理解進程內部的一個執行流?

Linux中,一個線程,在進程內部運行?線程在進程的虛擬地址空間中運行!!!


如何理解輕量化?

讓不同的"線程”訪問虛擬地址空間鐘的一部分資源!

如何做到?讓不同的線程未來執行不同的入口函數!


  • 觀點:進程是系統分配資源的基礎單位(內核角度,給進程下的定義),線程是CPU調度的基本單位

我認為單從上面的概念來看,我們是會一頭霧水的,只有等講解到底層完成時才能反過來更好理解以上的含義.

線程背景

線程的提出本質上是為了解決進程在併發場景下的效率挑戰和資源浪費問題,是操作系統在 “併發能力” 和 “資源開銷” 之間尋找平衡的產物。

既然已經提出了"線程"該場景的需求,自然也需要有各OS去實現對應的線程.而大家説一個進程中可能存在多個線程,線程也必須被調度,被切換等,因此也必須對線程進行管理,如何管理?

先描述,在組織 --> struct TCB --> 線程控制塊

實現容器化管理線程TCB,也要有運行隊列、等待隊列、掛起,調度算法.

那麼,大家真的有必要單獨創建 “線程” 嗎?它的行為模式看起來與進程頗為相似,這難道不是在做無用功嗎?

實際上,不同操作系統對線程的搭建思路存在差異:在 Windows 系統中,確實明確實現了 “線程” 這一獨立概念;而在 Linux 系統中,線程的構建方式更為巧妙 —— 它通過創建多個 PCB(進程控制塊),讓這些 PCB 共同指向同一個進程的虛擬地址空間。每個 PCB 會負責執行該進程代碼區中的特定部分,且僅訪問分配給自身的資源區域,這便是 Linux 系統中 “線程” 的本質實現形式

Linux多線程的構建 -- 內核角度

linux 線程 進程 - 草尖舞者的個人空間 -_虛擬地址_05

先描述,在組織-> struct TCB -> 線程控制塊實現容器化管理線程TCB, 調度算法。

Linux系統,內核中,線程的實現,是用進程模擬的,複用了進程代碼和結構!!!

linux 線程 進程 - 草尖舞者的個人空間 -_執行流_06

我們之前提到,每個進程都需要分配一個 PCB;但從現在的視角來看,每個進程實際上至少需要一個 PCB。這些 PCB 會被放入 CPU 的運行隊列,等待調度執行。

CPU看到的都叫做執行流: task struct 粒度<= 傳統的進程(談及執行流,CPU角度:不區分進程、線程,統一叫做,輕量級進程!!!)

此時我們再來理解進程和線程:

什麼叫做進程?承擔分配資源的基本實體 --> 如何理解我們以前講的進程? --> 進程內部,只有一個執行流(線程)的進程

什麼叫做線程?OS調度的基本單位 --> 一個 task_struct 叫做線程

今天的進程 --> 進程內部,可以有1個或者多個(執行流)的進程(只有一個執行流的進程是一種特殊情況)

但我認為僅靠上面這些並不能夠支撐我們去更好地理解進程和線程,因此這裏講一個小故事:

在現實世界中,分配社會資源的核心單位是什麼?家庭.

有多個家庭成員的.母親負責每天早起做早餐,給家裏人提供餐飯;父親每天搭乘着地鐵趕路,從早到晚都在上班,給家裏人獻出經濟來源;孩子每天都要求去上學,每天好好學習,不讓父母來操心.就是一個家庭內部,

,有沒有發現大家都有一個相同的目的:過好日子.就是每一個家庭成員都各自在做着不同的事情.可

而這裏的家庭就是所謂的進程,家庭成員就是一個個的線程,線程被創建出來做不同的事情,而他們被創建出來的目標是為了達成一個目標 -- 過好日子.

一個家庭內部,有多個成員 ---進程內具有多個執行流(線程);

一個家庭內部,只有一個成員!!

pthread庫

有沒有對應的指令可以查看一個進程內部有多少個輕量級進程呢?

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

linux 線程 進程 - 草尖舞者的個人空間 -_執行流_07

linux 線程 進程 - 草尖舞者的個人空間 -_執行流_08

進程vs線程, 線程其他理論話題

面試題

與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多!少體現在哪些地方呢?

linux 線程 進程 - 草尖舞者的個人空間 -_多線程_09

  • 線程切換不需要切換CR3寄存器,因為CR3保存頁表基地址,只要CR3不切就是線程間切換,切了就叫進程間切換;
  • TLB->緩存虛擬到物理地址 ---線程間切換,TLB不需要更新(進程間切換,TLB要重新緩存)
  • 進程內的多線程切換,cache緩存,不用更新 ; 但是進程間切換,就要重新把cache緩衝區“熱起來
  • 進程間切換,虛擬地址空間就切了,所付出的成本更大;
  • CPU也是要統計每個線程的時間片,怎麼知道?線程的pid是相等的,進程的所有線程的時間片被花完了,那麼當前進程pid找到所有線程創建新線程,時間片會被均分.(OS怕被偷時間片)
  • 線程切換仍需保存和恢復硬件上下文,但進程間切換不也需要麼;

cat /proc/cpuinfo

/proc/cpuinfo由內核動態生成,存儲了當前系統中 CPU 的詳細信息。其中就記錄了cache緩存.

線程優點

  • 創建⼀個新線程的代價要⽐創建⼀個新進程小得多
  • 與進程之間的切換相比,線程之間的切換需要操作系統做的⼯作要少很多
  1. 最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上下⽂切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能損耗是將寄存器中的內容切換出。
  2. 另外⼀個隱藏的損耗是上下⽂的切換會擾亂處理器的緩存機制。簡單的説,⼀旦去切換上下⽂,處理器中所有已經緩存的內存地址⼀瞬間都作廢了。還有⼀個顯著的區別是當你改變虛擬內存空間的時候,處理的⻚表緩衝 TLB (快表)會被全部刷新,這將導致內存的訪問在⼀段時間內相當的低效。但是在線程的切換中,不會出現該問題,當然還有硬件cache。
  • 線程佔⽤的資源要⽐進程少很
  • 能充分利用多處理器的可並⾏數量
  • 在等待慢速I/O操作結束的同時,腳本可執⾏其他的計算任務
  • 計算密集型應⽤,為了能在多處理器平台上運⾏,將計算分解到多個線程中實現(不要太多)
  • 通過I/O密集型應用,為了提⾼性能,將I/O運行重疊。線程能夠同時等待不同的I/O操作(許可多一些)。
  • 建議是cpu個數*核數,大部分操作都是內存級操作,假如創建太多了,額外的增加OS調度線程時切換線程的成本

線程缺點

  • 增加了額外的同步和調度開銷,⽽可⽤的資源不變。就是性能損失:⼀個很少被外部事件阻塞的計算密集型線程往往⽆法與其它線程共享同⼀個處理器。若是計算密集型線程的數量⽐可⽤的處理器多,那麼可能會有較⼤的性能損失,這⾥的性能損失指的
  • 健壯性降低:編寫多線程得更全⾯更深⼊的考慮,在⼀個多線程腳本⾥,因時間分配上的細微偏差或者因共享了不該共享的變量⽽造成不良影響的可能性是很⼤的,換句話説線程之間是缺乏保護的。
  • 缺乏訪問控制:進程是訪問控制的基本粒度,在⼀個線程中調⽤某些OS函數會對整個進程造成影響。
  • 編程難度提⾼:編寫與調試⼀個多線程程序⽐單線程程序困難得多

線程異常

  • 單個線程如果出現除零,野指針問題導致線程崩潰,進程也會隨着崩潰
  • 線程是進程的執⾏分支,線程出異常,就類似進程出異常,進⽽觸發信號機制,終⽌進程,進程終⽌,該進程內的所有線程也就隨即退出

線程用途

  • 合理的使⽤多線程,能提⾼CPU密集型程序的執行效率
  • 合理的使⽤多線程,能提⾼IO密集型程序的用户體驗(如⽣活中我們⼀邊寫代碼⼀邊下載開發⼯具,就是多線程運行的⼀種表現)

linux 線程 進程 - 草尖舞者的個人空間 -_多線程_10

如今我們終於可能理解,一個線程出現異常,整個進程為何會崩潰?

  1. 線程是代表一個進程;
  2. 線程出現異常,OS就會給進程發送信號,就會被殺掉,而進程的資源就會被釋放,而線程用的資源是進程給予的,沒有生存空間.

哪些資源共享,哪些獨佔

  • 進程間具有獨立性(進程更強調獨佔,偶爾要有共享)
  • 線程共享地址空間,也就共享進程資源(線程更強調共享,偶爾要有獨佔)

進程和線程

進程是資源分配的基本單位

線程是調度的核心單位

必答的,只有調度,,才需要切換上下文;除了被調用,還會產生各種臨時數據,也要有函數調用,棧幀結構:就是線程共享進程數據,但也擁有⾃⼰的⼀部分資料,對於前3點

  • 線程ID
  • ⼀組寄存器
  • errno
  • 信號屏蔽字
  • 調度優先級

進程的多個線程共享

通過共享的,如果定義⼀個函數,在各線程中都能夠調用,假設定義⼀個全局變量,在各線程中都能夠訪問到,除此之外,各線程還共享以下進程資源和環境:就是同⼀地址空間,因此Text Segment、Data Segment都

  • ⽂件描述符表
  • 每種信號的處理⽅式(SIG_ IGN、SIG_ DFL或者⾃定義的信號處理函數)
  • 當前⼯作⽬錄
  • 用户id和組id

進程和線程的關係如下圖:

linux 線程 進程 - 草尖舞者的個人空間 -_虛擬地址_11

總結

本文介紹了Linux系統中線程控制的核心概念與實現原理。主要內容包括:

  1. 藉助pthread_create函數創建線程的實踐方法;
  2. Linux內核採用進程模擬線程的獨特完成方式(輕量級進程);
  3. 進程與線程的區別與聯繫,強調進程是資源分配單位而線程是調度單位;
  4. 線程切換相比進程切換的性能優勢(如無需切換頁表、TLB等);
  5. 線程的應用場景與優缺點分析。文章藉助代碼示例和家庭比喻生動闡釋了多線程工作機制,並指出線程異常會導致整個進程終止的原因。末了總結了線程共享的進程資源和獨有的線程特性,支援讀者深入理解Linux多線程編程的本質。