我認(rèn)識(shí)的線程
開篇
1、背景
之前的很長(zhǎng)一段時(shí)間里,隨著加工工藝的發(fā)展,cpu的處理速度一直在提升,基本上每18個(gè)月就會(huì)翻倍。直到04年cpu主頻達(dá)到了4.0GH以來(lái),這種規(guī)律似乎已經(jīng)失效,原因是人們?cè)谥圃靋pu的工藝方面已經(jīng)達(dá)到了物理極限。除非技術(shù)有本質(zhì)突破,才能進(jìn)一步提高cpu的處理速度。然而需要處理的數(shù)據(jù)量并沒有因此而停止增長(zhǎng),其中的一個(gè)方法就是采用多核、并行處理技術(shù)。這會(huì)成為并且正在成為未來(lái)發(fā)展的趨勢(shì)。要理解并行技術(shù),對(duì)線程有一定的了解是很必要的。這篇博客主要說(shuō)一下自己對(duì)線程的看法,這只是從簡(jiǎn)單的角度來(lái)看問(wèn)題,入門級(jí)文章,筆者認(rèn)知有限,有不足之處還望不吝指正。
2、我的想法
關(guān)于并發(fā)編程,我覺得如果能有一種專門為并行而設(shè)計(jì)的語(yǔ)言,將是最好的解決方案。因?yàn)楝F(xiàn)有的語(yǔ)言大多的針對(duì)單核處理器的,現(xiàn)在的并發(fā)多數(shù)是由操作系統(tǒng)完成,用現(xiàn)行語(yǔ)言來(lái)編寫并發(fā)程序的技術(shù)還顯得不是很成熟。如果能有一種專門為并發(fā)而設(shè)計(jì)的語(yǔ)言,將會(huì)大幅度的提高程序的運(yùn)行效率。當(dāng)然這只是我的小想法而已。
3、內(nèi)容一覽:
1)程序和進(jìn)程的概念區(qū)別
2)線程的概念
3)多線程的調(diào)度問(wèn)題
4)線程安全問(wèn)題(當(dāng)多個(gè)線程同時(shí)訪問(wèn)一個(gè)變量時(shí))
程序和進(jìn)程
一般意義上的程序可以認(rèn)為是在特定操作系統(tǒng)上的可執(zhí)行文件。也就是源碼經(jīng)過(guò)編譯、鏈接而形成的可執(zhí)行文件,它依賴于執(zhí)行的操作系統(tǒng),因?yàn)檎遣僮飨到y(tǒng)提供了該程序運(yùn)行的環(huán)境(運(yùn)行庫(kù)等)。它是一個(gè)靜態(tài)的概念。
而進(jìn)程,是一個(gè)動(dòng)態(tài)的概念,它有自己的地址空間,能執(zhí)行一些操作。程序的執(zhí)行都會(huì)伴隨著進(jìn)程的生成,一個(gè)程序的執(zhí)行會(huì)產(chǎn)生一個(gè)或多個(gè)進(jìn)程。
所以可認(rèn)為進(jìn)程是程序的動(dòng)態(tài)概念。
總結(jié)下程序和進(jìn)程的區(qū)別
1)進(jìn)程是動(dòng)態(tài)的,而程序是靜態(tài)的
2)進(jìn)程有一個(gè)生命周期 ,而程序是指令集合,本身無(wú)“運(yùn)動(dòng)”含義。沒有建立進(jìn)程的程序不能作為一個(gè)單獨(dú)的單位得到操作系認(rèn)可
3)1個(gè)程序可產(chǎn)生多個(gè)進(jìn)程,一個(gè)進(jìn)程只對(duì)應(yīng)一個(gè)程序
什么是線程
線程有時(shí)候又被稱為輕量級(jí)進(jìn)程,是程序執(zhí)行的最小單元。和上文中一樣的,一個(gè)進(jìn)程可對(duì)應(yīng)多個(gè)線程,而一個(gè)線程只屬于一個(gè)進(jìn)程。
進(jìn)程的執(zhí)行是以線程為單位進(jìn)行得,比如說(shuō)一個(gè)簡(jiǎn)單“hello world”程序只有一個(gè)線程,就是main()函數(shù)對(duì)應(yīng)的線程。
線程的構(gòu)成:
1)線程ID。用于標(biāo)實(shí)線程
2)當(dāng)前指令指針PC。標(biāo)明下一指令執(zhí)行點(diǎn)
3)寄存器集合和堆棧。該線程的可用空間
多線程
大多數(shù)軟件應(yīng)用中,線程的數(shù)量都不止一個(gè)。多線程可用并發(fā)的執(zhí)行,并共享進(jìn)程的全局便來(lái)那個(gè)和堆的數(shù)據(jù)。
多線程優(yōu)勢(shì):
1)某個(gè)操作可能會(huì)陷入長(zhǎng)時(shí)間的等待。采用多線程,當(dāng)一個(gè)線程等待的時(shí)候,可執(zhí)行其他線程,充分利用cpu。
2)某操作(如計(jì)算)可能會(huì)消耗大量時(shí)間,而導(dǎo)致和用戶之間的交互中斷。多線程可讓一個(gè)線程負(fù)責(zé)計(jì)算,另一線程負(fù)責(zé)交互。
3)軟件本身就要求并發(fā)操作,如多端下載軟件
4)多核cpu,本身就具備同時(shí)執(zhí)行多個(gè)線程的能力
線程訪問(wèn)權(quán)限
一般來(lái)說(shuō)線程能訪問(wèn)進(jìn)程內(nèi)存中的所有數(shù)據(jù),但實(shí)際應(yīng)用中線程也有自己的空間
1)棧(可能被其他進(jìn)程訪問(wèn),但仍可認(rèn)為是私有數(shù)據(jù))
2)線程局部存儲(chǔ),一般只有很小容量
3)寄存器(包括PC寄存器)
線程調(diào)度
最好的情況是:當(dāng)處理器數(shù)量大于要處理的線程數(shù)目的時(shí)候,所有線程都可以同時(shí)執(zhí)行。實(shí)現(xiàn)真正意義上的并發(fā)。
而這種情況在現(xiàn)實(shí)中基本不可能。現(xiàn)實(shí)中的并發(fā)只是一種模擬出來(lái)的狀態(tài),特別是在單核處理對(duì)于多線程的時(shí)候。它通過(guò)讓多個(gè)線程交替執(zhí)行,每個(gè)線程執(zhí)行很短時(shí)間,從表面上看,這些線程同時(shí)執(zhí)行,實(shí)現(xiàn)并發(fā)。
每個(gè)線程都想被執(zhí)行,但是每次執(zhí)行的線程數(shù)量是有限的,所以就要有一種方法來(lái)從眾多的線程中選出要執(zhí)行的線程,現(xiàn)在討論下單核的情況,多核的類似。
在操作系統(tǒng)中有專門的線程調(diào)度算法來(lái)實(shí)現(xiàn),下面列幾個(gè)簡(jiǎn)單的“調(diào)度算法”
1、“先進(jìn)先出”策略。所有的線程組成一個(gè)隊(duì)列,新生的線程加入到隊(duì)列末尾,每次取隊(duì)頭執(zhí)行。有一個(gè)缺陷就是,如果新生成的線程是緊急操作,需要操作系統(tǒng)盡快相應(yīng),這種調(diào)度方法就不能滿足了。
2、按優(yōu)先級(jí)調(diào)度。每個(gè)線程都有自己的優(yōu)先級(jí),并且是可以被操作系統(tǒng)修改的,調(diào)度時(shí)候每次選取優(yōu)先級(jí)最高的執(zhí)行。這種方法彌補(bǔ)了上一種方法的缺陷。對(duì)于需要及時(shí)相應(yīng)的緊急事件,可以給他一個(gè)高優(yōu)先級(jí),這樣就能在下次被調(diào)度。然而這種方法也有一個(gè)問(wèn)題,也就是所謂的饑餓。如果某線程“看似”無(wú)關(guān)緊要,被給予一個(gè)低得優(yōu)先級(jí),以后每次產(chǎn)生的線程優(yōu)先級(jí)都比他高,那么這個(gè)線程會(huì)一直得不到執(zhí)行,成為餓死。一個(gè)解決的辦法是隨之事件的推移而提升線程的優(yōu)先級(jí)。這樣只要事件足夠長(zhǎng),低優(yōu)先級(jí)的線程也會(huì)獲得高優(yōu)先級(jí)而被執(zhí)行。
從上面的分析來(lái)開,線程似乎有兩個(gè)狀態(tài):執(zhí)行和不執(zhí)行(等待)。其實(shí)操作系統(tǒng)中的每個(gè)線程都對(duì)應(yīng)三個(gè)狀態(tài)。
線程的狀態(tài):
上面說(shuō)了線程調(diào)度的問(wèn)題,只有準(zhǔn)備就緒的線程(這種線程稱為就緒的線程),才能被調(diào)度。調(diào)度以后,線程就在處理器中被執(zhí)行,這時(shí)線程的狀態(tài)為運(yùn)行時(shí)。如果該線程在等待某種事件的發(fā)生(如響應(yīng)I/O),這種狀態(tài)成為等待。
總結(jié)下線程的三種狀態(tài):
1)就緒:此時(shí)線程可以立刻運(yùn)行i(如果該線程被調(diào)用的話)
2)運(yùn)行:此時(shí)線程正在執(zhí)行
3)等待:線程正在等待某件事的發(fā)生以便繼續(xù)執(zhí)行
在windows中,線程的狀態(tài)有:
已初始化(Initialized):說(shuō)明一個(gè)線程對(duì)象的內(nèi)部狀態(tài)已經(jīng)初始化,這是線程創(chuàng)建過(guò)程中的一個(gè)內(nèi)部狀態(tài),此時(shí)線程尚未加入到進(jìn)程的線程鏈表中,也沒有啟動(dòng)。
.
個(gè)線程來(lái)執(zhí)行時(shí),它只考慮處于就緒狀態(tài)的線程。此時(shí),線程已被加入到某個(gè)處理器的就緒線程鏈表中。
運(yùn)行(Running):線程正在運(yùn)行。該線程一直占有處理器,直至分到的時(shí)限結(jié)束,或者被一個(gè)更高優(yōu)先級(jí)的線程搶占,或者線程終止,或者主動(dòng)放棄處理器執(zhí)行權(quán),或者進(jìn)入等待狀態(tài)。
備用(Standby):處于備用狀態(tài)的線程已經(jīng)被選中作為某個(gè)處理器上下一個(gè)要運(yùn)行的線程。對(duì)于系統(tǒng)中的每個(gè)處理器,只能有一個(gè)線程可以處于備用狀態(tài)。然而,一個(gè)處于備用狀態(tài)的線程在真正被執(zhí)行以前,有可能被更高優(yōu)先級(jí)的線程搶占。
已終止(Terminated):表示線程已經(jīng)完成任務(wù),正在進(jìn)行資源回收。KeTerminateThread函數(shù)用于設(shè)置此狀態(tài)。
等待(Waiting):表示一個(gè)線程正在等待某個(gè)條件,比如等待一個(gè)分發(fā)器對(duì)象變成有信號(hào)狀態(tài),也可以等待多個(gè)對(duì)象。當(dāng)?shù)却臈l件滿足時(shí),線程或者立即開始運(yùn)行,或者回到就緒狀態(tài)。
轉(zhuǎn)移(Transition):處于轉(zhuǎn)移狀態(tài)的線程已經(jīng)準(zhǔn)備好運(yùn)行,但是它的內(nèi)核棧不在內(nèi)存中。一旦它的內(nèi)核棧被換入內(nèi)存,則該線程進(jìn)入就緒狀態(tài)。
延遲的就緒(DeferredReady):處于延遲的就緒狀態(tài)的線程也已經(jīng)準(zhǔn)備好可以運(yùn)行了,但是,與就緒狀態(tài)不同的是,它尚未確定在哪個(gè)處理器上運(yùn)行。當(dāng)有機(jī)會(huì)被調(diào)度時(shí),或者直接轉(zhuǎn)入備用狀態(tài),或者轉(zhuǎn)到就緒狀態(tài)。因此,此狀態(tài)是為了多處理器而引入的,對(duì)于單處理器系統(tǒng)沒有意義。
門等待(GateWait):線程正在等待一個(gè)門對(duì)象。此狀態(tài)與等待狀態(tài)類似,只不過(guò)它是專門針對(duì)門對(duì)象而設(shè)計(jì)。
他們之間的轉(zhuǎn)換圖如下:

更多關(guān)于調(diào)度的知識(shí)可參見《現(xiàn)代操作系統(tǒng)》《windows內(nèi)核分析》
可搶占線程和不可搶占線程
可搶占線程就是說(shuō),在該線程用完自己的時(shí)間片以后,操作系統(tǒng)會(huì)強(qiáng)制把該線程切出,以便執(zhí)行其他線程。
而不可搶占線程則是線程不能強(qiáng)制切出,除非他自己放棄cpu的使用權(quán)而終止線程,而不是靠時(shí)間片的用盡而強(qiáng)制切出。不可搶占線程的線程切換時(shí)間是確定的,當(dāng)該線程自愿切出時(shí)發(fā)生。
線程安全
多線程并發(fā)時(shí),在訪問(wèn)數(shù)據(jù)方面會(huì)出現(xiàn)一些問(wèn)題。特別是當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量的時(shí)候。
下面將用一個(gè)例子來(lái)說(shuō)明可能出現(xiàn)的問(wèn)題:
線程A、線程B都對(duì)變量X進(jìn)行操作,操作順序如下:
1)線程A對(duì)X賦值
2)線程A對(duì)X自加
3)線程B使用X的值(比如說(shuō)把它賦值給另一個(gè)變量)
代碼經(jīng)過(guò)編譯之后,在處理器中執(zhí)行代碼時(shí),通常一個(gè)很簡(jiǎn)單的運(yùn)算(如自家運(yùn)算)都會(huì)被分為多個(gè)步驟執(zhí)行(指令流水)。
比如當(dāng)線程A對(duì)X自加運(yùn)算的時(shí)候,編譯后的自加運(yùn)算共分為三步,當(dāng)沒有執(zhí)行完這三步的時(shí)候,可能線程A就會(huì)被切出(比如說(shuō)有需要即時(shí)響應(yīng)的操作發(fā)生)。也就是說(shuō)線程A在對(duì)數(shù)據(jù)還沒處理完全的時(shí)候被切出了,這樣當(dāng)線程B執(zhí)行的時(shí)候,使用的X值將不是我們期望的值,顯然,發(fā)生了錯(cuò)誤。
解決策略:
上面問(wèn)題的出現(xiàn)本質(zhì)上是因?yàn)橐粋€(gè)不應(yīng)該被打斷的操作被強(qiáng)行中斷了,那么有一種解決的辦法就是設(shè)置一種規(guī)定一些操作,在執(zhí)行的時(shí)候不能被中斷。這樣就避免了操作還沒完成就被換出的情況。把這些簡(jiǎn)單的操作稱為原子的操作。windows中對(duì)于這種操作也有支持。
但是這種策略只適用于簡(jiǎn)單的情況。對(duì)于復(fù)雜的情況,我們用一種稱為同步與鎖的機(jī)制來(lái)實(shí)現(xiàn)。
同步與鎖
簡(jiǎn)單的說(shuō),就是在一個(gè)線程對(duì)數(shù)據(jù)訪問(wèn)結(jié)束之前,其他線程不能對(duì)這個(gè)數(shù)據(jù)進(jìn)行訪問(wèn)。這樣的話,對(duì)數(shù)據(jù)的訪問(wèn)就原子化了。
這種機(jī)制的實(shí)現(xiàn)也很簡(jiǎn)單:每個(gè)線程對(duì)數(shù)據(jù)訪問(wèn)的時(shí)候都會(huì)嘗試獲取鎖,當(dāng)訪問(wèn)結(jié)束后釋放。在獲取鎖的時(shí)候如果有線程在訪問(wèn)數(shù)據(jù),就會(huì)獲取失敗,這時(shí)候線程會(huì)等待, 直到訪問(wèn)數(shù)據(jù)的那個(gè)線程釋放鎖。
二元信號(hào)量
是最簡(jiǎn)單的一種鎖,它只有兩個(gè)狀態(tài):占用與非占用。它用于只能被一個(gè)線程訪問(wèn)的資源。只有資源狀態(tài)為非占用 的時(shí)候,才能被線程獲取,獲取之后修改資源狀態(tài)為占用,訪問(wèn)結(jié)束后修改資源狀態(tài)為非占用。
信號(hào)量
稍微復(fù)雜一些,他適用于可以被多個(gè)線程同時(shí)訪問(wèn)的資源。一個(gè)初始值為N=n的信號(hào)量能被n個(gè)線程同時(shí)訪問(wèn)。當(dāng)要訪問(wèn)數(shù)據(jù)的時(shí)候,先查看N值,(N的值代表還有多少個(gè)線程能訪問(wèn)資源)如果N值大于0,該線程能訪問(wèn)資源,線程進(jìn)入后把N值減一。當(dāng)訪問(wèn)結(jié)束后N值+1.
如果信號(hào)量的值小于0,則進(jìn)入等待狀態(tài)。
互斥量
和二元信號(hào)相似,資源只能被一個(gè)線程訪問(wèn),但是同一個(gè)信號(hào)量只能被獲取該信號(hào)量的線程釋放,也就是說(shuō)對(duì)于二元信號(hào)量,同一個(gè)互斥量可以被別的(任意)線程釋放。相對(duì)二元信號(hào)量來(lái)說(shuō)更嚴(yán)格了。
臨界區(qū)
是比互斥量更嚴(yán)格的同步手段。臨界區(qū)和上面的區(qū)別在于,互斥量和信號(hào)量在任何進(jìn)程都是可見的,也就是說(shuō),一個(gè)進(jìn)程創(chuàng)建了互斥量和信號(hào)量,在其他進(jìn)程都是可見的,而臨界區(qū)的作用范圍僅限于本進(jìn)程。
讀寫鎖
讀寫鎖用于更加通用的場(chǎng)合。對(duì)于同一個(gè)數(shù)據(jù),多個(gè)進(jìn)程同時(shí)讀是沒問(wèn)題的,但是如果有線程要對(duì)數(shù)據(jù)進(jìn)行修改,就要使用同步手段來(lái)避免出錯(cuò)。對(duì)于同一個(gè)讀寫鎖,有兩種獲取方式:
1、共享的,對(duì)數(shù)據(jù)只進(jìn)行讀操作,可以多個(gè)線程同時(shí)進(jìn)行
2、獨(dú)占的。會(huì)修改數(shù)據(jù),在修改完成之前,不能有其他線程操作數(shù)據(jù)
當(dāng)鎖處于自由狀態(tài)時(shí),以任何一種方式獲取鎖都會(huì)成功,并將鎖至于相應(yīng)的狀態(tài)。
如果鎖處于共享狀態(tài),那么其他與以共享方式獲取鎖的線程都能成功,共同讀數(shù)據(jù)。
對(duì)于獨(dú)占式獲取的,則要等到以共享方式獲取的所有線程釋放后(鎖重新回到自由狀態(tài))才能獲取。并且對(duì)于以獨(dú)占方式獲取的鎖,其他任何對(duì)鎖的請(qǐng)求都不會(huì)成功。
條件變量
條件變量類似于一個(gè)發(fā)令槍,可以有多個(gè)線程等待槍響,槍響的時(shí)候,這些等待槍響的線程會(huì)同時(shí)恢復(fù)執(zhí)行。發(fā)令槍何時(shí)響也可由線程來(lái)決定。
也就是說(shuō),條件變量可以讓多個(gè)線程等待某件事的發(fā)生,當(dāng)時(shí)間發(fā)生時(shí)(條件變量被喚醒),所有的線程可以一起恢復(fù)執(zhí)行。
小結(jié)
這篇文章主要介紹了線程的概念,然后簡(jiǎn)單的說(shuō)了線程調(diào)度,最后敘述了線程的安全性問(wèn)題,也是就是多線程共享資源時(shí)候的相關(guān)問(wèn)題。而對(duì)于實(shí)際的例子這里沒有給出。是因?yàn)榫W(wǎng)上關(guān)于線程的實(shí)例已經(jīng)有很多了。
寫這篇文章的目的是總結(jié)一下自己對(duì)線程的認(rèn)識(shí),從我的角度去理解線程,希望這篇文章能對(duì)各位園友有幫助。
參看資料:computer systems 《windows內(nèi)核分析》《程序員的自我修養(yǎng)》
如有轉(zhuǎn)載請(qǐng)注明出處:http://www.rzrgm.cn/yanlingyin/
一條魚~@ 博客園 2012-2-12
E-mail:yanlingyin@yeah.net

浙公網(wǎng)安備 33010602011771號(hào)