一汽绢、線程定義
什么是線程?《POSIX Threads Programming》中有一段話對線程的定義進行描述:
A thread is defined as an independent stream of instructions that can be scheduled to run as such by the operating system.
線程可以被認為是一個可以被獨立調(diào)度的實體佩谷,這個實體共享進程的地址空間百匆、文件描述符于微、代碼和數(shù)據(jù)暂衡,且擁有自己私有的棧、寄存器上下文浊服、和程序計數(shù)器统屈。
二、為什么要線程
我們在 github 上面給開源項目提交代碼的時候牙躺,按照 comment 格式都要寫 Motivation 這部分愁憔,我們今天討論線程這個存在,也要討論線程為什么存在孽拷。
在很多應(yīng)用中需要同時執(zhí)行多個任務(wù)吨掌,這些任務(wù)大部分甚至全部都可以相互獨立的并行的執(zhí)行。比如一個網(wǎng)絡(luò)代理乓搬,傳統(tǒng)的實現(xiàn)是用一個進程作為監(jiān)聽器來監(jiān)聽網(wǎng)絡(luò)端口思犁,當(dāng)有客戶端連接進來的時候代虾,當(dāng)前進程將會 fork 一個新的進程來處理客戶端的請求进肯。這種體系結(jié)構(gòu)不好的地方如下:
1、fork 系統(tǒng)調(diào)用對于操作系統(tǒng)來說是一個非常重的操作棉磨。
2江掩、每一個進程都有自己獨立的地址空間,進程間相互通信必須要通過標(biāo)準(zhǔn)的 IPC 技術(shù)來實現(xiàn),比如信號量环形、共享內(nèi)存策泣,這些操作是非常昂貴的、嚴重影響系統(tǒng)性能抬吟。
線程的出現(xiàn)就是為了解決這些問題萨咕,線程之間擁有共享的進程空間用于共享數(shù)據(jù)、也有自己獨立的運行空間類似一個輕量級的進程火本。
三危队、用戶空間與內(nèi)核空間
在理解用戶線程與內(nèi)核線程之前、我們有必要了解一下用戶空間與內(nèi)核空間「婆希現(xiàn)代操作系統(tǒng)的地址空間主要基于虛擬地址空間機制設(shè)計茫陆,和實際物理內(nèi)存大小沒關(guān)系,比如對于 32 位操作系統(tǒng)擎析,它的尋址空間為 2 的 32 次方也就是 4G簿盅,這里的尋址空間被稱為虛擬存儲空間。操作系統(tǒng)的核心是內(nèi)核揍魂,獨立于普通應(yīng)用程序桨醋,具有最高權(quán)限,可以訪問底層硬件設(shè)備以及受保護的空間愉烙,因此這部分包括驅(qū)動程序和操作系統(tǒng)讨盒。操作系統(tǒng)的設(shè)計者為了保證內(nèi)核的安全,將用戶進程設(shè)計為只有一定權(quán)限的程序步责,它不能夠操作內(nèi)核以及硬件返顺。操作系統(tǒng)將虛擬存儲空間劃分為兩部分,一部分是內(nèi)核空間蔓肯,一部分是用戶空間遂鹊。針對 Linux 操作系統(tǒng)而言,最高的 1G 字節(jié)供內(nèi)核使用蔗包,稱為內(nèi)核空間秉扑,較低的 3G 字節(jié)供給各個進程使用,被稱為用戶空間调限。進程可以通過系統(tǒng)調(diào)用進入內(nèi)核舟陆,Linux 內(nèi)核由所有進程共享。用戶空間和內(nèi)核空間示意圖如下:
四耻矮、用戶態(tài)與內(nèi)核態(tài)
每個進程都擁有所有的虛擬地址空間秦躯,當(dāng)進程運行用戶代碼的時候是運行在用戶地址空間的,這時候 CPU 運行所需要的指令和數(shù)據(jù)都保存在用戶空間裆装,進程可以認為是指令 + 數(shù)據(jù) + CPU踱承,因此這個時候我們把這個狀態(tài)的進程叫做用戶進程倡缠。當(dāng)用戶執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行的時候,當(dāng)前進程運行的指令和數(shù)據(jù)都在內(nèi)核空間茎活,因此我們把這個狀態(tài)的進程叫內(nèi)核進程昙沦。用戶進程和內(nèi)核進程不是獨立的兩個進程的意思,而是進程運行的不同狀態(tài)载荔。值得注意的是盾饮,用戶進程不能訪問內(nèi)核虛擬地址空間,內(nèi)核進程可以訪問全部的虛擬地址空間懒熙,因此用戶進程和內(nèi)核進程進行數(shù)據(jù)交換只能通過內(nèi)核進程從用戶地址空間取數(shù)據(jù)丐谋,然后放入用戶地址空間。
系統(tǒng)調(diào)用涉及到進程從用戶態(tài)到內(nèi)核態(tài)的切換(mode switch)煌珊,這個時候涉及到的切換主要是寄存器上下文的切換号俐,和通常所說的進程上下文切換不同,mode switch 的消耗相對要小很多定庵。
五吏饿、用戶線程與內(nèi)核線程
上面可以看出,用戶線程與內(nèi)核線程的區(qū)別主要在于指令與數(shù)據(jù)運行于不同虛擬地址空間蔬浙,用戶線程和內(nèi)核線程也可以叫做用戶空間線程和內(nèi)核空間線程猪落。用戶線程由用戶代碼支持,內(nèi)核線程由操作系統(tǒng)內(nèi)核支持畴博。
六笨忌、線程上下文切換
線程上下文切換和線程模態(tài)切換不是一個維度的東東,線程上下文切換講的是多線程之間因為調(diào)度器的調(diào)度俱病,而從一個線程正在被調(diào)度切換到另外一個線程被調(diào)度的事情官疲。線程上下文切換必須要保存線程執(zhí)行的寄存器狀態(tài)、棧信息亮隙、線程正文途凫、數(shù)據(jù)等,因此相對模態(tài)切換是比較重的操作溢吻。
七维费、線程模型
線程模型在不同的操作系統(tǒng)下的實現(xiàn)通常有三種,每種模型都有其優(yōu)點與缺點促王,下面我們來看看這三種線程模型犀盟。
1、用戶空間線程模型(M : 1)
一個多線程子系統(tǒng)有可能全部由用戶代碼實現(xiàn)蝇狼,這些線程的調(diào)度與切換全部發(fā)生在用戶地址空間阅畴,這種模型通常是由一個內(nèi)核線程和多個用戶線程組成。典型的實現(xiàn)是基于 POSIX 線程 draft 4题翰,OSF’DCE 是其中一種具體實現(xiàn)恶阴。一個用戶空間庫負責(zé)線程的創(chuàng)建、終止豹障、調(diào)度與同步冯事。這些線程對于操作系統(tǒng)內(nèi)核是透明的。
這種模型的好處是線程上下文切換都發(fā)生在用戶空間血公,避免的模態(tài)切換(mode switch)昵仅,從而對于性能有積極的影響。然而不好的地方是所有的線程基于一個內(nèi)核調(diào)度實體即內(nèi)核線程累魔,這意味著只有一個處理器可以被利用摔笤,在多處理環(huán)境下這是不能夠被接受的,本質(zhì)上垦写,用戶線程只解決了并發(fā)問題吕世,但是沒有解決并行問題。
還有一點梯投,如果線程因為 I/O 操作陷入了內(nèi)核態(tài)命辖,內(nèi)核態(tài)線程阻塞等待 I/O 數(shù)據(jù),則所有的線程都將會被阻塞分蓖,用戶空間也可以使用非阻塞而 I/O尔艇,但是還是有性能及復(fù)雜度問題。
2么鹤、內(nèi)核空間線程模型(1:1)
對于用戶空間線程模型终娃,所有的用戶線程都和特定的內(nèi)核線程進行交互,而內(nèi)核空間線程模型是每個用戶線程都和一個特定的內(nèi)核線程進行交互蒸甜,用戶線程和內(nèi)核線程是 1:1 的關(guān)系棠耕。典型的實現(xiàn)是將每個用戶線程映射到一個內(nèi)核線程上。
每個線程由內(nèi)核調(diào)度器獨立的調(diào)度柠新,所以如果一個線程阻塞則不影響其他的線程昧辽。然而,創(chuàng)建登颓、終止和同步線程都會發(fā)生在內(nèi)核地址空間搅荞,這可能會帶來較大的性能問題。在創(chuàng)建線程的時候內(nèi)核必須要進行內(nèi)存鎖的申請框咙,并負責(zé)調(diào)度線程咕痛,而且每個線程都要消耗有限的內(nèi)核資源,當(dāng)大量的線程被創(chuàng)建的時候喇嘱,體現(xiàn)的尤為明顯茉贡。值得夸獎的是,在多核處理器的硬件的支持下者铜,內(nèi)核空間線程模型支持了真正的并行腔丧,下面是內(nèi)核空間模型示意圖:
3放椰、內(nèi)核用戶空間線程模型(M : N)
內(nèi)核用戶空間線程模型中,內(nèi)核線程和用戶線程的數(shù)量比為 M : N愉粤,因此也通常被叫做 M : N 線程模型砾医,內(nèi)核用戶空間綜合了前兩種的優(yōu)點。
這種模型需要內(nèi)核線程調(diào)度器和用戶空間線程調(diào)度器相互操作衣厘,本質(zhì)上是多個線程被綁定到了多個內(nèi)核線程上如蚜,這使得大部分的線程上下文切換都發(fā)生在用戶空間,而多個內(nèi)核線程又可以充分利用處理器資源影暴,模型圖如下:
總結(jié)
最近在深入學(xué)習(xí)多線程的時候错邦,大量學(xué)習(xí)了網(wǎng)上很多大神發(fā)表的文章,總結(jié)成上面的文章型宙,歡迎大家指正撬呢。