2017年年初,我給本身定了一個小小的目標:學習Linux編程,並經過網絡來分享本身的學習心得。爲了完成這個小小的目標,我開始用經過寫文章來記錄個人學習心得,但願在年末時,我能完成24篇Linux相關的學習文檔,以實現我這個小小的目標。這是這個系列的第一篇文章,是我對最近學習Linux多線程的總結。編程
咱們來看看維基百科是如何對線程進行定義的:網絡
線程(英語:thread)是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠並行多個線程,每條線程並行執行不一樣的任務。在Unix System V及SunOS中也被稱爲輕量進程(lightweight processes),但輕量進程更多指內核線程(kernel thread),而把用戶線程(user thread)稱爲線程。多線程
一個進程能夠包含多個線程,這些線程有各自的調用棧(call stack),本身的寄存器環境(register context)以及本身的線程本地存儲(thread-local storage)。每條線程都是併發執行不一樣的任務。由於還沒寫進程相關的文章,這裏我就不進一步說明進程和線程的區別了,我會在進程或者單獨的文章中說明它們的異同。併發
線程在其生命週期中有四種狀態,分別是就緒、運行、阻塞和終止,下面這個表格解釋了這四種狀態:函數
狀態 | 含義 |
---|---|
就緒 | 線程可以運行,可是在等待可用的處理器 |
運行 | 線程正在運行,在多核系統中,可能同時有多個線程在運行 |
阻塞 | 線程在等待處理器之外的其餘條件 |
終止 | 線程從啓動函數中返回,或者調用pthread_exit函數,或者被取消 |
線程能夠在必定條件下轉換其狀態,下圖顯示了線程是如何轉換其狀態的:學習
要建立線程,咱們使用 pthread_create()
這個函數,函數說明以下:spa
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); args: pthread_t *thread : 指向新線程的線程號的指針,若是建立成功則將線程號寫回thread指向的內存空間 const pthread_attr_t *attr : 指向新線程屬性的指針 void *(*start_routine) (void *): 新線程要執行函數的地址(回調函數) void *arg : 指向新線程要執行函數的參數的指針 return: 線程建立的狀態,0是成功,非0失敗
每個成功建立的線程都有一個線程號,咱們能夠經過 pthread_self()
這個函數獲取調用這個函數的線程的線程號。有一點咱們須要注意,若是須要編譯 pthread_XX()
相關的函數,咱們須要在編譯時加入 -pthread
。操作系統
要結束一個線程,咱們一般有如下幾種方法:線程
方法 | 說明 |
---|---|
exit() | 終止整個進程,並釋放整個進程的內存空間 |
pthread_exit() | 終止線程,但不釋放非分離線程的內存空間 |
pthread_cancel() | 其餘線程經過信號取消當前線程 |
exit()
會終止整個進程,所以咱們幾乎不會使用它來結束線程。咱們經常使用 pthread_cancel()
和 pthread_exit()
函數來結束線程。這裏,咱們重點看看 pthread_exit()
這個函數:指針
void pthread_exit(void *retval); args: void *retval: 指向線程結束碼的指針 return: 無
要結束一個線程,只須要在線程調用的函數中加入 pthread_exit(X)
便可,但有一點須要特別注意:若是一個線程是非分離的(默認狀況下建立的線程都是非分離)而且沒有對該線程使用 pthread_join()
的話,該線程結束後並不會釋放其內存空間。這會致使該線程變成了「殭屍線程」。「殭屍線程」會佔用大量的系統資源,所以咱們要避免「殭屍線程」的出現。
在上一部分咱們提到了 pthread_join()
這個函數,在這一節,咱們先來看看這個函數:
int pthread_join(pthread_t thread, void **retval); args: pthread_t thread: 被鏈接線程的線程號 void **retval : 指向一個指向被鏈接線程的返回碼的指針的指針 return: 線程鏈接的狀態,0是成功,非0是失敗
當調用 pthread_join()
時,當前線程會處於阻塞狀態,直到被調用的線程結束後,當前線程纔會從新開始執行。當 pthread_join()
函數返回後,被調用線程纔算真正意義上的結束,它的內存空間也會被釋放(若是被調用線程是非分離的)。這裏有三點須要注意:
被釋放的內存空間僅僅是系統空間,你必須手動清除程序分配的空間,好比 malloc()
分配的空間。
一個線程只能被一個線程所鏈接。
被鏈接的線程必須是非分離的,不然鏈接會出錯。
在上一節中,咱們在談 pthread_join()
時說到了只有非分離的線程才能使用 pthread_join()
,這節咱們就來看看什麼是線程的分離。在Linux中,一個線程要麼是可鏈接的,要麼是可分離的。當咱們建立一個線程的時候,線程默認是可鏈接的。可鏈接和可分離的線程有如下的區別:
線程類型 | 說明 |
---|---|
可鏈接的線程 | 可以被其餘線程回收或殺死,在其被殺死前,內存空間不會自動被釋放 |
可分離的線程 | 不能被其餘線程回收或殺死,其內存空間在它終止時由系統自動釋放 |
咱們能夠看到,對於可鏈接的線程而而言,它不會自動釋放其內存空間。所以對於這類線程,咱們必需要配合使用 pthread_join()
函數。而對於可分離的函數,咱們就不能使用 pthread_join()
函數。
要使線程分離,咱們有兩種方法:1)經過修改線程屬性讓其成爲可分離線程;2)經過調用函數 pthread_detach()
使新的線程成爲可分離線程。
下面是 pthread_detach()
函數的說明:
int pthread_detach(pthread_t thread); args: pthread_t thread: 須要分離線程的線程號 return: 線程分離的狀態,0是成功,非0是失敗
咱們只須要提供須要分離線程的線程號,即可以使其由可鏈接的線程變爲可分離的線程。
在上面的幾節中,咱們講了線程的建立,結束和鏈接。這節咱們來看一個特殊的線程 - 主線程。
在C程序中, main(int argc, char **argv)
就是一個主線程。咱們能夠在主線程中作任何普通線程能夠作的事情,但它和通常的線程有有一個很大的區別:主線程返回或者運行結束時會致使進程的結束,而進程的結束會致使進程中全部線程的結束。爲了避免讓主線程結束全部的線程,根據咱們以前所學的知識,有這麼幾個解決辦法:
不讓主線程返回或者結束(在 return
前加入 while(1)
語句)。
調用 pthread_exit()
來結束主線程,當主線程 return
後,其內存空間會被釋放。
在 return
前調用 pthread_join()
,這時主線程將被阻塞,直到被鏈接的線程執行結束後,才接着運行。
在這三種方法中,前兩種不多被使用,第三種是經常使用的方法。
這篇文章主要介紹了線程的基本概念,線程的生命週期和狀態,線程的建立、結束、鏈接、分離以及主線程。在下一篇中,我將介紹多線程中的重點 - 同步。
若是以爲本文對你有幫助,請多多點贊支持,謝謝!