前言
本篇博客梳理關于線程相關的Q&A,包括了線程概念與線程的控制。若讀者也在復習這塊知識,或者正在學習這塊知識,可以通過這些Q&A檢測自己的知識掌握情況。此外,思維導圖已經(jīng)更新至我的gitee,Q&A之外的體系梳理還請移步思維導圖。
Q&A
線程概念
Q:線程和進程的區(qū)別?(為什么要有線程,從進程的角度說明這個問題)
A:Linux用task_struct結構體描述一個進程,可以說進程 = task_struct + 內存中的數(shù)據(jù)與代碼。而task_struct包含了進程頁表、進程地址空間等資源,創(chuàng)建一個進程就需要為它分配這些資源。值得思考的是:分配資源本身就需要消耗資源,進程是否能充分利用分配得到的資源?而且由于進程具有獨立性,進程間想要共享或發(fā)送數(shù)據(jù),就要進行進程間通信,但通信的成本過高,會消耗過多資源,降低系統(tǒng)的性能。
若操作系統(tǒng)只有進程,那么進程就是系統(tǒng)的基本執(zhí)行流。
- 要執(zhí)行不同的程序,就要創(chuàng)建新的進程,但是創(chuàng)建進程會帶來資源的消耗,這是其一
- 若一個進程要使用另一個進程的數(shù)據(jù),就要進行進程間通信,通信的代價也是額外資源的消耗,這是其二
為解決以上兩個問題(最主要的兩個),線程被引入,線程作為進程的一部分,和進程共享進程地址空間(資源),同時解決了進程間通信與反復創(chuàng)建進程帶來的資源消耗問題。
Q:Linux是如何設計線程的?
A:學習進程時,我們說:進程 = task_struct + 內存中的數(shù)據(jù)與代碼,這個說法默認了task_struct是進程控制塊,也就是說task_struct是為了進程而設計的結構。但是事實并不是這樣,準確的說task_struct是Linux中的一個執(zhí)行流。若一個進程下沒有線程(或者說唯一的線程就是自己),此時的進程就是一個執(zhí)行流。若一個進程下有多個線程,此時的進程就不再是執(zhí)行流,此時的執(zhí)行流是進程下的線程,進程是多個執(zhí)行流的集合。
所以task_struct即不是為進程設計的,也不是為線程設計的,它是為執(zhí)行流這個概念設計的,你也可以極端點,認為Linux下沒有進程與線程的概念,Linux只有執(zhí)行流的概念。提出進程和線程只是為了更好的理解操作系統(tǒng)。
回到問題,你可以認為Linux只有執(zhí)行流的概念,它對應的結構體為task_struct。但是為了使多個執(zhí)行流可以同時使用相同的資源而不沖突,Linux肯定是要對線程進行設計的。我們可以通過if else判斷fork的返回值,控制父子進程,使兩進程分別執(zhí)行不同的代碼塊(函數(shù)),從而使它們的函數(shù)棧分離,體現(xiàn)在進程地址空間上,兩進程就是使用了不同的空間。這樣的思想也體現(xiàn)在線程的設計中,Linux設計了thread_struct結構體,其中有一組變量維護了寄存器地址,這些寄存器則維護了一個函數(shù)棧。所以,每個線程都享有一個獨立的函數(shù)棧(地址空間),這樣就解決了線程間數(shù)據(jù)沖突的問題。
總結一下:Linux用thread_struct結構體表示線程結構,該結構體中最重要的是一組寄存器地址,這些寄存器維護了線程的函數(shù)棧,使線程享有獨立的資源,互不沖突。
Q:學習了線程后,你能說說進程和線程最大的區(qū)別是什么嗎?
A:兩者最大的區(qū)別就是:承擔的職責不同
- 進程是系統(tǒng)中資源分配的基本單位,系統(tǒng)分配進程地址空間、頁表等結構,消耗了大量資源
- 線程是系統(tǒng)中調度的基本單位,系統(tǒng)分配資源給進程,線程使用進程的一部分資源,以執(zhí)行任務
進程和線程的比較
Q:線程使用進程的資源,它們之間的所有資源都是共享的嗎?有哪些資源不是共享的?
A:
- 線程獨享的資源
- 線程ID:需要用不同的線程ID標識同一進程下的不同線程
- 一組寄存器與函數(shù)棧:為了防止線程之間發(fā)送數(shù)據(jù)沖突,線程需要維護自己的函數(shù)棧
- errno:每個線程必須獨享errno以便在程序崩潰時更快定位錯誤
- 信號屏蔽字:每個線程可以設置自己想要屏蔽的信號
- 調度優(yōu)先級:可以設置線程的優(yōu)先級以調整線程的執(zhí)行順序
- 線程與進程共享的數(shù)據(jù)
- 文件描述符表:線程和進程打開的文件,彼此都能看到
- 信號的遞達方式:線程和進程設置的信號遞達方式也會彼此影響
- 當前工作路徑:進程與使用其資源的線程在同一工作路徑下運行
- 用戶id和組id:進程與使用其資源的線程擁有相同的owner和group
Q:線程的優(yōu)缺點分別是什么?
A:
- 優(yōu)點:
- 充分使用進程的資源,盡可能的減少不必要進程的創(chuàng)建,提高系統(tǒng)性能
- 線程之間數(shù)據(jù)共享,比起進程間通信,這是一種更高效的通信方式
- 創(chuàng)建線程的代價小于進程,因為系統(tǒng)不要為線程分配頁表、進程地址空間這樣的資源
- 切換線程的代價小于進程,因為系統(tǒng)不需要重新加載頁表、進程地址空間,只需要重新加載task_struct結構體以及線程的函數(shù)棧
- 充分利用多處理器的可并行數(shù)量
- 在含有慢速IO的進程中,可以創(chuàng)建線程等待慢速IO的結束,使進程可以執(zhí)行其他任務,不必等待慢速IO的結束
- 在密集IO的進程中,可以創(chuàng)建多個線程等待IO的結束,使等待時間重疊,有效提高了程序的運行效率
- 缺點:
- 健壯性較差:線程崩潰退出會導致進程的退出
- 調試難度大:多線程程序下的錯誤難以定位
線程操作
Q:線程終止的三種方式分別是什么?
A:
- 直接return,不過返回的對象要強轉為(void*)
- 調用int pthread_exit(void* retval)退出,該函數(shù)的參數(shù)是一個類型為void*的變量
- 調用int pthread_cancel(pthread_t thread)向指定線程發(fā)送cancel信號,該線程的返回值為-1
Q:為什么pthread_self()的返回值和LWP不一樣?
A:LWP(light weight process),輕量級進程,使用ps -aL可以查看線程的LWP。而pthread_self()的返回值(線程ID)遠遠大于LWP,其原因是:
-
Linux沒有提供線程的操作接口,或者說這些接口不夠簡便,需要自己設置于管理線程的屬性。我們的線程操作基于第三方庫,第三方庫幫助我們設置與管理線程的屬性
-
第三方庫使用struct thread_info結構體存儲線程的信息,而這些結構存儲在進程地址空間的共享區(qū),線程ID的值就等于這些結構體的首地址
-
可以通過程序驗證,線程ID的值小于棧區(qū)地址,大于堆區(qū)地址
-
LWP是內核的一個概念,內核用LWP對輕量級進程進行管理
-
線程ID是地址空間的一個概念,通過線程ID可以找到線程的屬性,從而對線程進程管理(這是我們通過線程庫對線程的間接管理)
Q:為什么要進行線程分離?
A:主線程創(chuàng)建的子線程默認具有joinable屬性,若主線程不主動join子線程,會造成資源泄漏與線程句柄的耗盡。線程分離后,主線程不用join子線程,子線程結束,其資源會自動釋放。
這個問題也能理解為:為什么要join子線程?兩個原因:一個是回收子線程的資源,一個是得到子線程的返回值,前者是必要的,而后者是非必要的。當主線程不再關心子線程的返回信息時,主線程可以主動分離該線程。
Q:為什么要由主線程分離子線程?不能子線程自己分離?
A:這是為了防止一些bug的產(chǎn)生,也是一種編程規(guī)范。若子線程調用pthread_detach分離自己,主線程無法確定子線程什么時候被分離,如果主線程在在子線程調用pthread_detach之前調用pthread_join該線程,那么主線程會陷入阻塞,但由于該線程被分離,不會向主線程返回,所以主線程會陷入永久的阻塞,程序因此產(chǎn)生bug。文章來源:http://www.zghlxwxcb.cn/news/detail-412972.html
所以不能讓子線程調用pthread_detach分離自己,這會帶來一些不確定性。同時我們也要確保主線程不要對將要分離或已經(jīng)分離的子線程做任何操作。文章來源地址http://www.zghlxwxcb.cn/news/detail-412972.html
到了這里,關于Linux復習 / 線程相關----線程概念與控制 Q&A梳理的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!