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