線程

1. 什么是線程

在上一篇進(jìn)程里已經(jīng)講到,可以將一個程序里互不影響又能單獨(dú)拆分出來的任務(wù)放到新進(jìn)程里執(zhí)行,但每個進(jìn)程都有獨(dú)立的地址空間,資源是互相隔離的(如果不用共享內(nèi)存的話),那么如果想用多個進(jìn)程同時處理一個文件怎么辦呢?由此引入了Thread線程的概念。線程就是進(jìn)程中的一段代碼片段,該代碼片段可以和其他代碼片段并發(fā)執(zhí)行,除了一些線程獨(dú)有的執(zhí)行信息,線程能夠共享進(jìn)程的所有資源。

2. 為什么需要線程

  1. 可以將一個進(jìn)程中IO操作和CPU操作分離開,既能共享地址空間和文件數(shù)據(jù)又能提升效率
    比如一個文件編輯的應(yīng)用,用一個線程處理用戶輸入,一個線程定時寫入硬盤實現(xiàn)自動保存,一個線程在后臺做拼寫檢查。如果只有一個線程,保存時就不能處理輸入或檢查,只能順序處理。
  2. 創(chuàng)建線程的開銷遠(yuǎn)低于進(jìn)程
  3. 充分利用多核CPU或多CPU計算機(jī)的計算能力

3. 線程模型

經(jīng)典線程模型

除了線程執(zhí)行過程信息是線程獨(dú)有的,其他都是和process共享的。下表第二列是線程獨(dú)有的信息,稱作Thread Control Block (TCB):program counter是線程要執(zhí)行的下一條指令的地址;registers保存了當(dāng)前執(zhí)行指令的數(shù)據(jù)或訪存地址等;方法調(diào)用棧中每一個棧幀就是一個未返回的方法調(diào)用,保存了局部變量和返回地址;線程的狀態(tài)的進(jìn)程一樣也分為running, block, ready和terminated.

Per-process items Per-thread items
Address space Program counter
Global variables Registers
Open files Stack
Child processes State
Pending Alarms
Signals and signal handlers
Accounting information

線程的生命周期

  1. 創(chuàng)建線程:thread_create
  2. 終止線程:thread_exit
  3. 等待其他線程終止:thread_join,當(dāng)前線程進(jìn)入block狀態(tài)
  4. 主動放棄CPU:thread_yield, 當(dāng)前線程進(jìn)入ready狀態(tài)

POSIX 線程

為了能編寫出標(biāo)準(zhǔn)化的可移植的多線程程序,IEEE定義了一個標(biāo)準(zhǔn),Pthreads. 這個標(biāo)準(zhǔn)定義了60多個方法,我們介紹其中幾個主要的:

  1. Pthread_create
    創(chuàng)建一個線程并返回線程標(biāo)識符
  2. Pthread_exit
    終止一個線程并釋放它的stack
  3. Pthread_join
    當(dāng)前線程進(jìn)入block狀態(tài),等待其他線程執(zhí)行結(jié)束
  4. Pthread_yield
    主動放棄CPU使用權(quán),進(jìn)入ready狀態(tài)
  5. Pthread_attr_init
    創(chuàng)建一個線程相關(guān)的屬性結(jié)構(gòu),并初始化為默認(rèn)值
  6. Pthread_attr_destroy
    刪除線程的屬性結(jié)構(gòu)信息,釋放內(nèi)存資源,但線程本身仍然存在

4. 線程實現(xiàn)

線程可以在用戶空間或內(nèi)核空間實現(xiàn),也可以混合實現(xiàn),接下來我們分別討論不同的實現(xiàn)及其優(yōu)缺點。

在用戶空間實現(xiàn)線程

線程完全在用戶空間實現(xiàn),OS 內(nèi)核對線程的存在一無所知,對OS來說它管理的還是一個個進(jìn)程。進(jìn)程需要自己管理線程,維護(hù)thread table和TCB。
優(yōu)點

  1. 可以在不支持線程的OS上執(zhí)行
  2. 線程切換不用切換到內(nèi)核態(tài)執(zhí)行,節(jié)省開銷,速度更快
  3. process可以自定義線程調(diào)度算法

缺點

  1. 實現(xiàn)阻塞式系統(tǒng)調(diào)用變得復(fù)雜:比如一個線程等待鍵盤輸入時,不能調(diào)用OS提供的blocking system call,因為這會導(dǎo)致整個process 被block
  2. page fault會導(dǎo)致process被block: 如果一個線程遇到了缺頁錯誤,OS會將整個process block去做訪存操作,即使其他的線程可以處在ready狀態(tài)。
  3. 由于時鐘中斷作用不到process內(nèi)部,線程之間切換只能依賴于線程主動放棄CPU使用權(quán)

在內(nèi)核空間實現(xiàn)線程

線程在kernel實現(xiàn),由kernel管理thread table和TCB。線程的創(chuàng)建和終止都通過system call實現(xiàn)。
優(yōu)點

  1. 可能導(dǎo)致線程block的調(diào)用都由system call實現(xiàn)
  2. 一個thread block之后,調(diào)度器可以選擇相同process中的其他線程繼續(xù)執(zhí)行,也可以選擇不同process的線程執(zhí)行

缺點

  1. 每次system call切換到kernel開銷巨大:針對這個缺點的解決方案是使用線程池,一個線程執(zhí)行結(jié)束之后不直接銷毀而是進(jìn)入idle狀態(tài)等待新的任務(wù)。

混合實現(xiàn)

為了平衡用戶空間和內(nèi)核空間實現(xiàn)線程的優(yōu)缺點,可以采用在用戶空間和內(nèi)核空間混合實現(xiàn)的方式,開發(fā)人員決定使用多少kernel thread 和 user-level thread。

編寫多線程程序要考慮的問題

  1. 如何處理屬于某個線程的全局變量,而讓其他線程不可見
    比如errno. thread1 要打開一個文件前去檢查對文件的permission,OS返回結(jié)果到errno, 此時CPU使用權(quán)轉(zhuǎn)到thread2, thread2執(zhí)行操作也向全局變量errno寫入覆蓋了thread1的結(jié)果,thread1恢復(fù)執(zhí)行以后去errno取到錯誤的結(jié)果導(dǎo)致執(zhí)行失敗。
    解決辦法:每個thread維護(hù)自己的私有全局變量
  2. 如何避免多個線程同時進(jìn)入一個不可重入的方法,比如malloc
  3. signal問題:比如一個鍵盤中斷發(fā)出signal又沒有指定線程時,應(yīng)該由哪個線程來捕獲這個信號
  4. 棧管理問題:一個進(jìn)程棧溢出時,OS會自動分配stack空間,但如果一個進(jìn)程擁有多個線程,每個線程都有自己的??臻g,如果kernel不知道這些stack的存在就無法正確地自動分配更多空間。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容