1. 程式人生 > >多線程編程基礎

多線程編程基礎

str 存在 文件描述符 ttr 不同 目錄 ict idp 頭文件

一、線程概念

1、引入

我們知道,進程在各自獨立的地址空間中運行,進程之間共享數據需要用mmap或者進程間通信機制,本篇我們將學習如何在一個進程的地址空間中執行多個線程。有些情況需要在一個進程中同時執行多個控制流程,這時候線程就派上了用場,比如實現一個圖形界面的下載軟件, 一方面需要和用戶交互,等待和處理用戶的鼠標鍵盤事件,另一方面又需要同時下載多個文件, 等待和處理從多個網絡主機發來的數據,這些任務都需要一個“等待-處理”的循環,可以用多線程實現,一個線程專門負責與用戶交互,另外一個線程負責和一個網絡主機通信。

2、什麽叫線程

在一個程序裏的多個執行路線就叫做線程。更準確的定義是:線程是“一個進程內部的一個控制序列

”。典型的unix進程可以看成只有一個控制線程:一個進程在同一時刻只做一件事情。有了多個控制線程以後,在程序設計時可以把進程設計成在同一時刻能夠做不止一件事,每個線程處理各只獨立的任務。線程可以看作是輕量級進程,它是操作系統調度的基本單位。main函數和信號處理函數是同一個進程地址空間中的多個控制流程,多線程也是如此,但是比信號處理函數更加靈活,信號處理函數的控制流程只是在信號遞達時產生,在 處理完信號之後就結束,而多線程的控制流程可以長期並存,操作系統會在各線程之間調度和切換,就像在多個進程之間調度和切換一樣。

3、線程特性

同一進程的多個線程共享同一地址空間。其中Text Segment、 Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:(1)線件描述符表;(2)每種信號的處理方式(SIG_IGN、 SIG_DFL或者自定義的信號處理函數);(3)當前工作目錄;(4)用戶id和組id。但有些資源是每個線程獨有一分的:(1)線程id;(2)上下文,包括各種寄存器的值、程序計數器和棧指針;(3)棧空間;(4) errno變量;(5)信號屏蔽字;(6)調度優先級。


4、線程的優缺點

優點:(1)通過為每種事件類型的處理分配單獨的線程,能夠簡化處理異步時間的代碼;
(2)多個線程可以自動共享相同的存儲地址空間和文件描述符;
(3)有些問題可以通過將其分解從而改善整個程序的吞吐量;
(4)交互的程序可以通過使用多線程實現相應時間的改善,多線程可以把程序中處理用戶輸入輸出的部分與其它部分分開。

缺點:線程也有不足之處。編寫多線程程序需要更全面更深入的思考。在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的。調試一個多線程程序也比調試一個單線程程序困難得多。

二、線程控制

1、線程的創建

名稱:pthread_create
功能:創建線程
頭文件:#include <pthread.h>
函數原形:int pthread_create(pthread_t *thread,const pthread _attr_t *attr,void *(*start_routine)(void*),void *restrict arg);
參數:第一個參數thread指id
返回值:若成功返回則返回0,否則返回錯誤編號

技術分享

當pthread_creat成功返回時, tidp指向的內存單元被設置為新創建線程的線程ID。attr參數用於定制各種不同的線程屬性。可以把它設置為NULL,創建默認的線程屬性。新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針參數arg,如果需要向start_rtn函數傳遞的參數不止一個,那麽需要把這些參數放到一個結構中,然後把這個結構的地址作為arg參數傳入。

技術分享

關於進程的編譯我們都要加上參數 –lpthread 否則提示找不到函數的錯誤
具體編譯方法是 gcc –lpthread –o pthread pthread.c
運行結果為

技術分享

以前學過的系統函數都是成功返回0,失敗返回-1,將錯誤號保存在全局變量errno中,而pthread庫的函數都是通過返回值返回錯誤號,雖然每個線程也都有一個errno,但這是為了兼容其它函數接口而提供的,pthread庫本身並不使用它,通過返回值返回錯誤碼更加清晰。
在一個線程中調用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)得到子進程的退出狀態, 後面在詳細介紹pthread_join。

3、名稱:pthread_self
功能:獲取自身線程的id
頭文件:#include <pthread.h>
函數原形:pthread_t pthread_self(void);
參數:無
返回值:調用線程的線程id

技術分享上例中就已經用到了這個函數。

3、線程的終止

線程是依進程而存在的,當進程終止時,線程也就終止了。當然也有在不終止整個進程的情況下停止它的控制流。
1)從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit。
2) 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
3)線程可以調用pthread_exit終止自己。 用pthread_cancel終止一個線程分同步和異步兩種情況,比較復雜,後面再詳細介紹。

(1)名稱:pthread_exit
功能:終止一個線程
頭文件:#include <pthread.h>
函數原形:void pthread_exit(void *rval_ptr);
參數:rval_prt是一個無類型指針,與傳給啟動例程的單個參數類似。進程中的其他線程可以調用pthread_join函數訪問到這個指針。
返回值:無

技術分享

retval是void *類型,和線程函數返回值的用法一樣,其它線程可以調用pthread_join獲得這個指針。 需要註意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。

(2)線程等待

名稱:pthread_join
功能:調用該函數的線程將掛起等待,直到id為thread的線程終止。
頭文件:#include <pthread.h>
函數原形:int pthread_join(pthread_t thread,void **rval_ptr);
參數:
返回值:若成功返回0,否則返回錯誤編號。

技術分享

thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
1. 如果thread線程通過return返回,value_ptr所指向的單元內存放的是thread線程函數的返回值。
2. 如果thread線程被別的線程調用pthread_cancel異常終掉,value_ptr所指向的單元裏存放的是常數PTHREAD_CANCELED。
3. 如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。 如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr 參數。
當一個線程通過調用pthread_exit退出或者簡單地從啟動歷程中返回時,進程中的其他線程可以通過調用pthread_join函數獲得進程的退出狀態調用pthread_join進程將一直阻塞,直到指定的線程調用pthread_exit,從啟動例程中或者被取消。
如果線程只是從它的啟動歷程返回,rval_ptr將包含返回碼。

技術分享

技術分享

輸出結果如下:

技術分享

可見在Linux的pthread庫中常數PTHREAD_CANCELED的值是-1。可以在頭文件pthread.h 中找到它的定義。
一般情況下,線程終止後,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止。 但是線程也可以被置為detach 狀態,這樣的線程一旦終止就立刻回收它占用的所有資源, 而不保留終止狀態。不能對一個已經處於detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL。 對一個尚未detach的線程調用pthread_join或pthread_detach都可以把該線程 置為detach狀態,也 就是說,不能對同一線程調用兩次pthread_join,或者如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。技術分享返回值:成功返回0,失敗返回錯誤號。




多線程編程基礎