歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> Linux線程詳解

Linux線程詳解

日期:2017/3/3 12:53:39   编辑:Linux技術
5.1、線程相關概念
《1》發展:線程是在進程之後發展起來的,因為多進程的一些缺點,從而引入線程這個概念。一個進程可以有多個線程。
《2》多進程優缺點
<2.1> 優點:CPU分時復用,單核心CPU可以實現宏觀上的並行,從而相當於多進程的“同時”做任務。
<2.2> 缺點:【1】多進程占內存,父子進程擁有兩套一模一樣的程序代碼,而對於子進程來說有些資源根本用不到,浪費內存資源。
【2】進程切換開銷大:cpu由進程1切換到進程2執行,過程開銷大。進程切換時要切頁表,而且往往伴隨著頁調度,因為進程的數據段代碼段要換出去,以便把將要執行的進程的內容換進來,這是OS調度的機制。
【3】進程之間通信麻煩且效率低
《3》多線程優點:【1】多線程共享一套程序代碼,相比多進程節約內存資源
【2】多線程切換速度快:只需要保存線程的上下文(相關寄存器狀態和棧的信息)就好了,動作很小;
【3】通信容易:因為共享一套程序代碼
【4】多線程在多核心cpu上更具有優勢:一個進程如果有四線程,則四核心cpu就可以一起做這個進程的任務,而單進程只能在一個核心上干,其他核心想干干不了。
《4》源分派的最小單位是進程,cpu調度的最小單位是線程。
《5》pthread : Linux中有posix 標准類別的線程,可能還有其他類別的;
《6》一個進程至少有一個線程;也就是說當一個進程被創建後,OS會自動幫這個進程創建一個線程,稱之為主線程。子進程和父進程互相獨立,各自自動創建自己的主線程。
《7》同一個進程的多線程有各自的棧、資源空間; 主線程能搞死子線程,子線程搞不死主線程且子線程可以互相搞死,主線程死了,子線程也死了。
5.2、涉及到線程的函數
《1》線程創建與回收
<1.0> 回收:是指線程結束之後,其線程所占有的資源需要主線程來回收,和子進程一樣,但線程可以設置自己結束自己回收機制。如果進程沒有去回收線程資源,則進程死了後,由系統幫忙來回收資源。
<1.1> pthread_create函數:
創建子線程;比如 pthread_create( &tid , NULL , thr_fun1 , NULL) ; 其中第二個參數為線程屬性指針,將此參數設為NULL,這也就使用了線程的默認屬性- 非分離狀態( joinable,或稱可接合狀態 ),
之後,主線程必須在適當的時候調用pthread_join( ) 回收子線程。
<1.2> pthread_join
(主線程調用) 主線程用來等待(阻塞式)回收子線程;
<1.3> pthread_detach
(子線程調用) 主線程用來分離子線程,分離後主線程不必再去回收子線程,子線程自己結束後會自己回收,如果主線程陷入死循環,則無法去回收子線程,所以最好使用分離子線程。
<1.4> 還有一種方式可以實現線程自己回收自己,詳情見:http://www.cppblog.com/prayer/archive/2012/04/23/172427.html ;
<1.5> pthread_selfid 函數 獲取線程ID
<1.6> 詳見代碼 test1.c
《2》線程結束
<2.1> 正常結束:
【1】pthread_exit函數(這是線程的標准返回)
【2】return 退出;
【3】注意:不能調用exit( ) ; 這是進程退出。上面兩種一般情況下是一樣的,但是最新實驗結果表明使用return不會執行pthread_cleanup_push+pthread_cleanup_pop 而pthread_exit()會執行pthread_cleanup_push+pthread_cleanup_pop ,所以最好用標准返回。
pthread_exit( ) , return 都可以用pthread_join來接收返回值的:(pthread_exit ((void *8);改為return (void*)8;效果一樣) ;所以以後就死心地用pthread_exit就好。
【4】詳情看:http://bbs.chinaunix.net/thread-2080486-1-1.html
<2.2> 異常結束:
【1】pthread_cancel
函數 :主線程結束線程;一般都是主線程調用該函數去結束(讓它趕緊死)子線程;可以分類:立即殺死+稍後死(比如手裡有互斥鎖,要調用pthread_cleanup_push裡面參數的函數) 。
【2】pthread_setcancelstate 函數 :子線程可以設置自己是否允許被其他線程結束
【3】pthread_setcanceltype 函數;子線程可以設置自己被結束的類型:立即+稍後;這個首先要開啟允許被搞死。
<2.3> 自動解鎖——在執行同步、互斥代碼過程中,線程突然死了 ,這時候裡面的互斥鎖還沒有解鎖,就會導致這個信號量憑空"消失“,任何線程都用不了了, 這時候就應該調用這個函數,監聽作用,來實現線程結束時先要執行這個參數裡的函數來自動解鎖。
【1】pthread_cleanup_push 入棧 線程結束(正常結束即pthread_exit、return 退出;異常結束即其他線程結束了這個進程【這個要代碼測試】)結束之前如果調用了這個函數,則會執行這個函數的參數函數。然後結束進程。
【2】pthread_cleanup_pop 彈棧 如果調用了上面push函數,而且中途,一定是中途,不是整個線程結束,沒有收到一個結束信號,則應該調用pop清理棧,把入棧的函數給取出來,不要污染了棧。參數0只取不執行,參數1取了還要執行。
【3】詳見代碼 test2.c
5.3、多線程同步
《1》概念:受到其他線程的制約,按照先後順序來干事情。比如:有一個buf,一個進程來放,另一個進程來取,順序不能亂,放了只能取,不能放了又放,取了又取。 不受系統隨機調度而打亂次序。
《2》思路:假如要讓線程1先執行,則線程2必須走起來就被一種手段所阻塞住,此時就只能讓線程1先執行了,線程1執行完後第一要讓線程2通過某種手段喚醒線程2,第二它自己必須要讓線程2執行,才能再次執行,也就是說
線程1執行完後不能重復執行,等待線程2調度執行才能繼續執行線程1。所以線程1走起來也應該要通過某種手段阻塞自己,那麼當線程2執行完後,就要喚醒線程1了,此時線程2走起來被某種手段所阻塞,則自己就不能
重復執行自己了。這個手段必須是同類別不同個體。
《3》如何設計
<3.1> 信號量
【1】簡介:信號量意思是代表可用資源的數量,是一個計數器;比如有3個打印機,此時信號就初始化3。信號量為原子操作,其不能被打斷執行過程,哪怕時間片到了都不行。
【2】涉及到的函數(原子函數):http://blog.chinaunix.net/uid-21714580-id-172149.html //sem為指向信號量結構的一個指針;pshared不為0時此信號量在進程間(比如父子)共享,否則只能為當前進程的所有線程共享;value給出了信號量的初始值。
【3】實質:實質是判斷信號量是否大於0來是否繼續往下執行或者阻塞掛起。
【4】實現同步方式:詳見代碼 test3.c
<3.2> 互斥量信號——又叫互斥鎖(mutex)
【1】介紹:互斥信號原本是用來達到多線程不能同時訪問一段資源(代碼段、共享數據、獨占設備打印機等)的目的,這段資源稱之為臨界資源;也就是說多線程在某時刻只能有一個線程訪問臨界資源。比如只有一個廁所,多人只能在某時刻有一個人上。
【2】用法:互斥信號量就是訪問臨界資源的一個權限,哪個線程拿到了這個權限就哪個訪問資源。得到權限後,先上鎖,然後用完後解鎖,讓其他排隊線程按隊列順序得到權限再訪問,比如上廁所,先關門,再使用,然後解鎖,讓其它排隊的人使用。
【3】目的:保證邏輯性、安全性,比如使用打印機需要互斥訪問,其多線程不能同時訪問打印機,否則打印出來的東西就是亂的。
【4】結合:可以結合信號量( 初始值>0)用來設計同類多個臨界資源的訪問,比如線程可以互斥使用5台打印機來工作
【5】涉及到的函數——原子函數
(1)pthread_mutex_init 函數:初始化互斥信號量;
(2)pthread_mutex_destroy 函數:銷毀一個互斥信號量。
(3)pthread_mutex_lock 函數:互斥加鎖
(4)pthread_mutex_unlock 函數:互斥解鎖
(5)詳情:http://blog.chinaunix.net/uid-23193900-id-3196775.html
【6】注意:man 3 pthread_mutex_init時提示找不到函數,說明你沒有安裝pthread相關的man手冊。安裝方法:1、虛擬機上網;2、sudo apt-get install manpages-posix-dev
【6】實現同步方式:詳見代碼 test4.c
<3.3> 條件變量信號
【1】介紹:某個線程發起(成立)這個條件信號,其余線程因為這個條件不滿足的阻塞掛起而被系統激活,成立這個條件然後才能繼續往下執行。
【2】特點:第一、它可以同時激活多個因為此條件而阻塞的線程,也可以只激活排隊中因為此條件而阻塞的線程;根據需要可以編程選擇那種方式;既然條件變量信號能激活多線程,說明多線程能同時”自減“操作;
如果都”自減“則這個條件變量就亂了,所以條件變量被視為臨界資源,pthread_cond_wait 時要用互斥方式去訪問,當然這個條件變量涉及到線程只有一個,則可以不加。
第二、必須先因為此條件而阻塞的線程才能獲得發起的條件信號,也就是說先發起這個條件信號,此刻沒有任何因此條件而被阻塞的線程所要激活,則這個發起的條件視為無效,即發起條件和判斷條件數量不對等。
【3】涉及到的函數
pthread_cond_init 函數:初始化條件變量信號
pthread_cond_destroy 函數:銷毀條件變量信號
pthread_cond_wait 函數:內部實質是先判斷數是否大於0,如果是自減,再執行下面的程序。 函數一般要使用互斥訪問
pthread_cond_signal/pthread_cond_broadcast 函數:內部是自加一個數來達到條件成立,使得數大於0;發出成立條件變量信號; 這個函數不需要加互斥 ;
【4】實現同步方式:詳見代碼test5.c
《4》三種方式對比;用條件變量信號有弊端,因為它的特點2需要先被阻塞,然後由發起成功條件信號而被激活,一般操作系統會調度成千上萬個線程,當線程1執行完後,發起成功條件信號,如果線程2沒有及時被調度成為此條件到阻塞;
那麼線程1的成立條件就會失效,線程1等待線程2給它成立條件,而線程2被調度後沒有有效的成立條件激活,這樣就形成了死鎖。
《5》注意事項:
線程中用到了同步、互斥代碼,從安全、可靠性要使用pthread_cleanup_push函數來實現自動解鎖。詳見代碼。
5.5、雜談
《1》 編譯時要加線程庫函數; -lpthread 或者 -pthread ;用後者更好,前者有時候要報錯。
《2》cpu多核心是指:一個SOC裡面有四個MPU核心,共用一套SOC內設。
《3》線程有涉及到同步和互斥的,為了安全、可靠性,都應該要寫自動解鎖程序——pthread_cleanup_push、pthread_cleanup_pop
《4》互斥量必須在同一個線程中獲取和釋放,否則會死鎖。條件變量和信號量沒有這個限制。
《5》信號量、互斥信號量、條件信號量內部實質都是通過原子操作++和-- 對信號量的操作。
5.6、完整代碼鏈接地址(360雲盤):https://yunpan.cn/cPa4KkGpU5I5w 提取代碼:a924
Copyright © Linux教程網 All Rights Reserved