Linux編程學習筆記 | Linux多線程學習[1] - 線程的建立和基本控制

文章系列緣由

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() 函數返回後,被調用線程纔算真正意義上的結束,它的內存空間也會被釋放(若是被調用線程是非分離的)。這裏有三點須要注意:

  1. 被釋放的內存空間僅僅是系統空間,你必須手動清除程序分配的空間,好比 malloc() 分配的空間。

  2. 一個線程只能被一個線程所鏈接。

  3. 被鏈接的線程必須是非分離的,不然鏈接會出錯。

線程的分離

在上一節中,咱們在談 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) 就是一個主線程。咱們能夠在主線程中作任何普通線程能夠作的事情,但它和通常的線程有有一個很大的區別:主線程返回或者運行結束時會致使進程的結束,而進程的結束會致使進程中全部線程的結束。爲了避免讓主線程結束全部的線程,根據咱們以前所學的知識,有這麼幾個解決辦法:

  1. 不讓主線程返回或者結束(在 return 前加入 while(1) 語句)。

  2. 調用 pthread_exit() 來結束主線程,當主線程 return 後,其內存空間會被釋放。

  3. return 前調用 pthread_join() ,這時主線程將被阻塞,直到被鏈接的線程執行結束後,才接着運行。

在這三種方法中,前兩種不多被使用,第三種是經常使用的方法。

總結

這篇文章主要介紹了線程的基本概念,線程的生命週期和狀態,線程的建立、結束、鏈接、分離以及主線程。在下一篇中,我將介紹多線程中的重點 - 同步。

若是以爲本文對你有幫助,請多多點贊支持,謝謝!

相關文章
相關標籤/搜索