Linux线程详解(概念、原理、实现方法、优缺点)
文章目錄
- 一、Linux線程基本概念
- 二、Linux內(nèi)核線程實(shí)現(xiàn)原理
- 三、創(chuàng)建線程
- 四、線程的優(yōu)缺點(diǎn)
一、Linux線程基本概念
linux中,線程又叫做輕量級(jí)進(jìn)程(light-weight process LWP),也有PCB,創(chuàng)建線程使用的底層函數(shù)和進(jìn)程底層一樣,都是clone,但沒(méi)有獨(dú)立的地址空間;而進(jìn)程有獨(dú)立地址空間,擁有PCB。
Linux下:線程是最小的執(zhí)行單位,調(diào)度的基本單位。進(jìn)程是最小分配資源單位,可看成是只有一個(gè)線程的進(jìn)程。
線程是一個(gè)進(jìn)程內(nèi)部的控制序列。控制序列可以理解為一個(gè)執(zhí)行流。進(jìn)程內(nèi)部是指虛擬地址空間。
1、線程特點(diǎn):
(1)線程是資源競(jìng)爭(zhēng)的基本單位。
操作系統(tǒng)有很多資源。進(jìn)程與進(jìn)程之間要競(jìng)爭(zhēng)操作系統(tǒng)資源,當(dāng)一個(gè)進(jìn)程申請(qǐng)得到一大堆資源。而這些資源又會(huì)分配給線程。一個(gè)進(jìn)程內(nèi)部有多個(gè)線程,去競(jìng)爭(zhēng)進(jìn)程所獲得的資源。所以說(shuō)線程是資源競(jìng)爭(zhēng)的基本單位。
(2)線程是程序執(zhí)行的最小單位
當(dāng)用戶讓進(jìn)程去執(zhí)行某個(gè)任務(wù)時(shí),進(jìn)程又會(huì)將任務(wù)細(xì)化。進(jìn)程內(nèi)部有很多線程,讓這些線程去執(zhí)行
(3)線程共享進(jìn)程數(shù)據(jù),但也擁有自己獨(dú)立的一部分?jǐn)?shù)據(jù): 線程ID ,一組寄存器,棧,errno值,信號(hào)。
其中最重要的數(shù)據(jù)是棧和寄存器。私有棧是為了保存臨時(shí)變量,便于函數(shù)調(diào)用等操作。私有寄存器是為了方便線程切換,保存上下文。
2、進(jìn)程到線程:
進(jìn)程:承擔(dān)分配系統(tǒng)資源的實(shí)體
線程:共享進(jìn)程所獲得資源
二、Linux內(nèi)核線程實(shí)現(xiàn)原理
創(chuàng)建線程使用的底層函數(shù)和進(jìn)程一樣,都是clone。從內(nèi)核里看進(jìn)程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內(nèi)存資源的三級(jí)頁(yè)表是相同的。進(jìn)程可以蛻變成線程。線程可看做寄存器和棧的集合。
三級(jí)映射:進(jìn)程PCB --> 頁(yè)目錄(可看成數(shù)組,首地址位于PCB中) --> 頁(yè)表 --> 物理頁(yè)面 --> 內(nèi)存單元
對(duì)于進(jìn)程來(lái)說(shuō),相同的地址(同一個(gè)虛擬地址)在不同的進(jìn)程中,反復(fù)使用而不沖突。原因是他們雖虛擬址一樣,但,頁(yè)目錄、頁(yè)表、物理頁(yè)面各不相同。相同的虛擬址,映射到不同的物理頁(yè)面內(nèi)存單元,最終訪問(wèn)不同的物理頁(yè)面。但線程不同!兩個(gè)線程具有各自獨(dú)立的PCB,但共享同一個(gè)頁(yè)目錄,也就共享同一個(gè)頁(yè)表和物理頁(yè)面。所以兩個(gè)PCB共享一個(gè)地址空間。
實(shí)際上,無(wú)論是創(chuàng)建進(jìn)程的fork,還是創(chuàng)建線程的pthread_create,底層實(shí)現(xiàn)都是調(diào)用同一個(gè)內(nèi)核函數(shù)clone。如果復(fù)制對(duì)方的地址空間,那么就產(chǎn)出一個(gè)“進(jìn)程”;如果共享對(duì)方的地址空間,就產(chǎn)生一個(gè)“線程”。
因此:Linux內(nèi)核是不區(qū)分進(jìn)程和線程的。只在用戶層面上進(jìn)行區(qū)分。所以,線程所有操作函數(shù) pthread_* 是庫(kù)函數(shù),而非系統(tǒng)調(diào)用。
三、創(chuàng)建線程
1、調(diào)用函數(shù):
返回值:成功返回0,失敗返回錯(cuò)誤碼。
2、pthread_create函數(shù)
創(chuàng)建一個(gè)新線程。其作用對(duì)應(yīng)進(jìn)程中fork() 函數(shù)。Linux環(huán)境下,所有線程特點(diǎn),失敗均直接返回錯(cuò)誤號(hào)。
參數(shù)說(shuō)明:
(1)thread :
pthread_t 類型的指針,線程創(chuàng)建成功的話,會(huì)將分配的線程 ID 添入該指針指向的地址。線程后續(xù)的操作將該值作為線程的唯一標(biāo)識(shí)。
(2)attr :
pthread_attr_t 類型,通過(guò)該參數(shù)可以定制線程屬性,比如可以指定新建線程棧空間的大小,調(diào)度策略等。如果要?jiǎng)?chuàng)建的線程無(wú)特殊要求,該值設(shè)置成 NULL,標(biāo)識(shí)采用默認(rèn)屬性。
(3)start_routine :
線程需要執(zhí)行的函數(shù)。創(chuàng)建線程是為了讓線程執(zhí)行特定的任務(wù)。線程創(chuàng)建成功之后,該線程就會(huì)執(zhí)行 start_routinue 函數(shù),該函數(shù)之于線程,就如同 main 函數(shù)之于主線程。
(4)arg :
線程執(zhí)行 start_routine 函數(shù)的參數(shù)。當(dāng)執(zhí)行函數(shù)需要傳入多個(gè)參數(shù)時(shí),線程創(chuàng)建者(一般是主線程)和新建線程約定一個(gè)結(jié)構(gòu)體,創(chuàng)建者把信息填入該結(jié)構(gòu)體,再把結(jié)構(gòu)體的指針傳給新建線程,新建線程只要解析這個(gè)結(jié)構(gòu)體,就能獲取到需要的所有參數(shù)。
在一個(gè)線程中調(diào)用pthread_create()創(chuàng)建新的線程后,當(dāng)前線程從pthread_create()返回繼續(xù)往下執(zhí)行,而新的線程所執(zhí)行的代碼由我們傳給pthread_create的函數(shù)指針start_routine決定。start_routine函數(shù)接收一個(gè)參數(shù),是通過(guò)pthread_create的arg參數(shù)傳遞給它的,該參數(shù)的類型為void *,這個(gè)指針按什么類型解釋由調(diào)用者自己定義。start_routine的返回值類型也是void *,這個(gè)指針的含義同樣由調(diào)用者自己定義。start_routine返回時(shí),這個(gè)線程就退出了,其它線程可以調(diào)用pthread_join得到start_routine的返回值,類似于父進(jìn)程調(diào)用wait(2)得到子進(jìn)程的退出狀態(tài)。
3、線程ID:
pthread_create 函數(shù)會(huì)產(chǎn)生一個(gè) pthread_t 類型的線程 ID,存放在第一個(gè)參數(shù)指向的空間內(nèi)。這里的線程 ID 和前面提到的 pid_t 類型的線程 ID 我們?cè)撊绾稳ザㄎ换蛘呖创?#xff1f;
pid_t 類型的線程 ID :屬于進(jìn)程調(diào)度的范疇。因?yàn)榫€程是輕量級(jí)進(jìn)程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個(gè)數(shù)值來(lái)在整個(gè)操作系統(tǒng)內(nèi)唯一標(biāo)識(shí)該線程。
pthread_t 類型的線程 ID :屬于NPTL線程庫(kù)的范疇,線程庫(kù)的后續(xù)操作,就是根據(jù)該線程 ID 來(lái)操作線程的。對(duì)于 Linux 目前使用的 NPTL 實(shí)現(xiàn)而言,pthread_t 類型的 ID 本質(zhì)上是進(jìn)程地址空間上的一個(gè)地址。
(1)pthread_self 函數(shù):可以獲取到線程自身的 ID。其作用對(duì)應(yīng)進(jìn)程中 getpid() 函數(shù)。
pthread_t pthread_self(void); 返回值:成功:0; 失敗:無(wú)!
線程ID:pthread_t類型,本質(zhì):在Linux下為無(wú)符號(hào)整數(shù)(%lu),其他系統(tǒng)中可能是結(jié)構(gòu)體實(shí)現(xiàn)。線程ID是進(jìn)程內(nèi)部,識(shí)別標(biāo)志。(兩個(gè)進(jìn)程間,線程ID允許相同)。注意:不應(yīng)使用全局變量 pthread_t tid,在子線程中通過(guò)pthread_create傳出參數(shù)來(lái)獲取線程ID,而應(yīng)使用pthread_self。
(2)pthread_equal函數(shù):
在同一個(gè)線程組內(nèi)的,線程庫(kù)提供了接口,可以判斷兩個(gè)線程 ID 是否對(duì)應(yīng)著同一個(gè)線程:
返回值是 0 的時(shí)候,表示是同一個(gè)線程,非 0 則表示不是同一個(gè)線程。
4、pthread_exit函數(shù):
參數(shù)value ptr:value ptr不要指向一個(gè)局部變量。
返回值:無(wú)返回值,跟進(jìn)程一樣,線程結(jié)束的時(shí)候無(wú)法返回到它的調(diào)用者(自身)。
pthread exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須是全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其它線程得到這個(gè)返回指針時(shí)線程函數(shù)已經(jīng)退出了。
調(diào)用phread_exit(),他會(huì)等待所有其他對(duì)等線程(就是同一個(gè)進(jìn)程中的除自身外其他線程)終止,然后再終止主線程和整個(gè)進(jìn)程。
5、clone函數(shù):
雖然在同一程序中創(chuàng)建的GNU / Linux線程是作為單獨(dú)的進(jìn)程實(shí)現(xiàn)的,但它們共享其虛擬內(nèi)存空間和其他資源。但是,使用fork創(chuàng)建的子進(jìn)程可以獲取這些項(xiàng)的副本。
Linux clone系統(tǒng)調(diào)用是fork和pthread_create的通用形式,它允許調(diào)用者指定在調(diào)用進(jìn)程和新創(chuàng)建的進(jìn)程之間共享哪些資源。
clone()的主要用途是實(shí)現(xiàn)線程:在共享內(nèi)存空間中并發(fā)運(yùn)行的程序中的多個(gè)控制線程。與fork()不同,這些調(diào)用允許子進(jìn)程與調(diào)用進(jìn)程共享其執(zhí)行上下文的一部分,例如內(nèi)存空間,文件描述符表和信號(hào)處理程序表。
使用clone()創(chuàng)建子進(jìn)程時(shí),它將執(zhí)行函數(shù)fn(arg)。 fn參數(shù)是指向子進(jìn)程在執(zhí)行開(kāi)始時(shí)調(diào)用的函數(shù)的指針。 arg參數(shù)傳遞給fn函數(shù)。
child_stack參數(shù)指定子進(jìn)程使用的堆棧的位置。
雖然屬于同一進(jìn)程組的克隆進(jìn)程可以共享相同的內(nèi)存空間,但它們不能共享相同的用戶堆棧。 因此,clone()調(diào)用為每個(gè)進(jìn)程創(chuàng)建單獨(dú)的堆棧空間
我們可以將clone視為進(jìn)程和線程之間共享的統(tǒng)一實(shí)現(xiàn)。Linux上進(jìn)程和線程之間的區(qū)別是通過(guò)將不同的標(biāo)志傳遞給克隆來(lái)實(shí)現(xiàn)的。差異主要在于這個(gè)新流程與啟動(dòng)它的流程之間共享的內(nèi)容。
CLONE_VM(since Linux 2.0):
如果設(shè)置了CLONE_VM,則調(diào)用進(jìn)程和子進(jìn)程在同一內(nèi)存空間中運(yùn)行。 特別是,由調(diào)用進(jìn)程或子進(jìn)程執(zhí)行的內(nèi)存寫(xiě)入在另一個(gè)進(jìn)程中也是可見(jiàn)的。
如果未設(shè)置CLONE_VM,則子進(jìn)程在clone()時(shí)在調(diào)用進(jìn)程的內(nèi)存空間的單獨(dú)副本中運(yùn)行。 其中一個(gè)進(jìn)程執(zhí)行的內(nèi)存寫(xiě)入不會(huì)影響另一個(gè)進(jìn)程,就像fork一樣。
在沒(méi)有vm命令行參數(shù)的情況下調(diào)用時(shí),CLONE_VM標(biāo)志關(guān)閉,父節(jié)點(diǎn)的虛擬內(nèi)存被復(fù)制到子節(jié)點(diǎn)中。 子節(jié)點(diǎn)看到父節(jié)點(diǎn)放在buf中的消息,但無(wú)論寫(xiě)入buf的是什么,都會(huì)進(jìn)入自己的副本而父節(jié)點(diǎn)無(wú)法看到它。
但是當(dāng)傳遞vm參數(shù)時(shí),會(huì)設(shè)置CLONE_VM并且子任務(wù)共享父級(jí)的內(nèi)存。 現(xiàn)在可以從父母那里看到它寫(xiě)入buf的內(nèi)容。
6、pthread_join函數(shù)
阻塞等待線程退出,獲取線程退出狀態(tài)。其作用對(duì)應(yīng)進(jìn)程中 waitpid() 函數(shù)。
int pthread_join(pthread_t thread, void **retval); 成功:0;失敗:錯(cuò)誤號(hào)
參數(shù):thread:線程ID (注意:不是指針);retval:存儲(chǔ)線程結(jié)束狀態(tài)。
注意:
參數(shù) retval 非空用法:
調(diào)用該函數(shù)的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過(guò)pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:
(1)如果thread線程通過(guò)return返回,retval所指向的單元里存放的是thread線程函數(shù)的返回值。
(2)如果thread線程被別的線程調(diào)用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數(shù)PTHREAD_CANCELED。
(3)如果thread線程是自己調(diào)用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數(shù)。
(4)如果對(duì)thread線程的終止?fàn)顟B(tài)不感興趣,可以傳NULL給retval參數(shù)。
四、線程的優(yōu)缺點(diǎn)
1、線程的優(yōu)點(diǎn):
(1)創(chuàng)建一個(gè)新線程的代價(jià)要比創(chuàng)建一個(gè)新進(jìn)程小得多,釋放成本也更低。因?yàn)閯?chuàng)建一個(gè)進(jìn)程就意味著要?jiǎng)?chuàng)建PCB,分配虛擬地址空間,頁(yè)表,物理內(nèi)存等系統(tǒng)資源,而創(chuàng)建一個(gè)線程只需要?jiǎng)?chuàng)建一個(gè)PCB(TCB)即可
(2)與進(jìn)程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多。進(jìn)程切換,需要切換對(duì)應(yīng)的虛擬地址空間,更換頁(yè)表等。過(guò)程繁瑣。
(3)線程占用的資源要比進(jìn)程少很多。
(4)能充分利用多處理器的可并行數(shù)量。
(5)在等待慢速I(mǎi)/O操作結(jié)束的同時(shí),程序可執(zhí)行其他的計(jì)算任務(wù)。
(6)計(jì)算密集型應(yīng)用,為了能在多處理器系統(tǒng)上運(yùn)行,將計(jì)算分解到多個(gè)線程中實(shí)現(xiàn) I/O密集型應(yīng)用,為了提高性能,將I/O操作重疊。線程可以同時(shí)等待不同的I/O操作。進(jìn)程可以將線程串行執(zhí)行變成并行執(zhí)行。最后匯總,提高效率。但是盡量不要?jiǎng)?chuàng)建太多線程,線程切換也是需要成本的。
2、線程的缺點(diǎn)
(1)性能損失:
一個(gè)很少被外部事件阻塞的計(jì)算密集型線程往無(wú)法與共它線程共享同一個(gè)處理器。如果計(jì)算密集型線程的數(shù)量比可用的處理器多,那么可能會(huì)有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開(kāi)銷(xiāo),而可用的資源不變。
(2)健壯性降低:
編寫(xiě)多線程需要更全面更深入的考慮,在一個(gè)多線程程序里,因時(shí)間分配上的細(xì)微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說(shuō)線程之間是缺乏保護(hù)的。一個(gè)線程掛掉,因?yàn)榫€程共享一塊資源。其他線程也會(huì)掛掉。進(jìn)而導(dǎo)致進(jìn)程退出,資源被回收。
(3)缺乏訪問(wèn)控制:
進(jìn)程是訪問(wèn)控制的基本粒度,在一個(gè)線程中調(diào)用某些OS函數(shù)會(huì)對(duì)整個(gè)進(jìn)程造成影響。多線程訪問(wèn)臨界資源,它的訪問(wèn)控制是由編程者決定。
(4)編程難度提高
編寫(xiě)與調(diào)試一個(gè)多線程程序比單線程程序困難得多,基于同步互斥。你需要不斷的加鎖。
總結(jié)
以上是生活随笔為你收集整理的Linux线程详解(概念、原理、实现方法、优缺点)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux线程的创建与回收
- 下一篇: Linux C/C++UDP通信实现