Linux系統(tǒng)編程(三) ------ 多線程編程

一、線程的創(chuàng)建和調(diào)度

1.線程是程序執(zhí)行的某一條指令流的映像。

為了進一步減少處理機制的空轉(zhuǎn)時間,支持多處理器及減少上下文切換開銷,進程在演化中出現(xiàn)了另一個概念——線程。它是進程內(nèi)獨立的一條運行路線,是處理器調(diào)用的最小單元,也可以成為輕量級進程。
進程標識符在內(nèi)部唯一,只為了進程內(nèi)的區(qū)分。
線程可以對進程的內(nèi)存空間和資源進程訪問,并與同一個進程中的其他線程共享。因此,雖然每個線程同樣得開辟一定大小空間擁有自己的??臻g,擁有獨立的執(zhí)行序列,但線程上下文切換的開銷比創(chuàng)建進程小得多。
在串行程序基礎(chǔ)上引入線程和進程是為了提高程序的并發(fā)度,從而提高程序運行效率和響應(yīng)時間。

線程和進程在使用上各有優(yōu)缺點:

線程執(zhí)行開銷小,但不利于資源的管理和保護;而進程正相反。同時,線程適合于在SMP機器上運行,而進程則可以跨機器遷移。

2.線程函數(shù),用于提供線程執(zhí)行的指令(代碼)。

向線程函數(shù)傳遞參數(shù)分為兩種:
1)線程函數(shù)只有一個參數(shù)的情況:直接定義一個變量通過應(yīng)用傳給線程函數(shù)。
2)線程函數(shù)有多個參數(shù)的情況:此時就必須申明一個結(jié)構(gòu)體來包含所有的參數(shù),然后在傳入線程函數(shù)。

3.線程庫

#include <pthread.h>```
Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。
pthread_t 在頭文件/usr/include/bits/pthreadtypes.h中定義:

    typedef unsigned long int pthread_t;
它是一個線程的標識符。在編譯命令末注意加上-lpthread參數(shù),以調(diào)用靜態(tài)鏈接庫。因為pthread并非Linux系統(tǒng)的默認庫。

####4.使用pthread_create()函數(shù)創(chuàng)建線程:
>函數(shù)原型
```c
int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void *(*start_routine)(void *), void *arg);```

*函數(shù)參數(shù)及說明:*
- 第一個參數(shù)為指向線程標識符的指針。返回成功時,由tid指向的內(nèi)存單元被設(shè)置為新創(chuàng)建線程的線程ID。
- 第二個參數(shù)用來設(shè)置各種不同的線程屬性。
- 第三個參數(shù)是線程運行函數(shù)的起始地址。新創(chuàng)建的線程從start_routine函數(shù)的地址開始運行,該函數(shù)只有一個萬能指針參數(shù)arg,如果需要向start_routine函數(shù)傳遞的參數(shù)不止一個,那么需要把這些參數(shù)放到一個結(jié)構(gòu)中,然后把這個結(jié)構(gòu)的地址作為arg的參數(shù)傳入。
- 最后一個參數(shù)是運行函數(shù)的參數(shù)。arg指針是最初唯一對指針所指向的對象進行存取的方法,僅當?shù)诙€指針基于第一個時,才能對對象進行存取。對對象的存取都限定于基于由arg指針表達式中。 由arg指針主要用于函數(shù)形參,或指向由 malloc() 分配的內(nèi)存空間。arg 數(shù)據(jù)類型不改變程序的語義。編譯器能通過作出arg指針是存取對象的唯一方法的假設(shè),更好地優(yōu)化某些類型的進程。

*函數(shù)返回值:*函數(shù)成功返回0。任何其他返回值都表示錯誤。

####5.使用pthread_join()函數(shù)以阻塞的方式等待thread指定的線程結(jié)束。
```c
 int pthread_join(pthread_t thread, void **retval);

函數(shù)參數(shù)及說明:

  • 第一個參數(shù)為被等待的線程標識符,標識唯一線程。
  • 第二個參數(shù)為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。

這個函數(shù)是一個線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當函數(shù)返回時,被等待線程的資源被收回。如果線程已經(jīng)結(jié)束,那么該函數(shù)會立即返回。
如果沒有pthread_join()主線程會很快結(jié)束從而使整個進程結(jié)束,從而使創(chuàng)建的線程沒有機會開始執(zhí)行就結(jié)束了。加入pthread_join()后,主線程會一直等待直到等待的線程結(jié)束自己才結(jié)束,使創(chuàng)建的線程有機會執(zhí)行。

函數(shù)返回值:函數(shù)執(zhí)行成功返回0。任何其他返回值都表示錯誤。

提示:

在多線程編程的時候我們往往都是以for循環(huán)的形式調(diào)用,實際上主線程在第1個線程處就掛起了,在等待1號線程結(jié)束后再等待2號線程。當然會出現(xiàn)3,4,5比1,2先結(jié)束的情況。主線程還是在等待1,2結(jié)束后,發(fā)現(xiàn)3,4,5其實早已經(jīng)結(jié)束了,就會回收3,4,5的資源,然后主線程再退出。

6.使用pthread_exit()函數(shù)終止并線程

void pthread_exit(void *retval);```
*函數(shù)說明及返回值:*
是線程的主動行為;由于一個進程中的多個線程是共享數(shù)據(jù)段的,因此通常在線程退出之后,退出線程所占用的資源并不會隨著線程的終止而得到釋放,但是可以用pthread_join()函數(shù)來配合同步并釋放資源。pthread_exit()終止調(diào)用它的線程并返回一個指向某個對象的指針,該返回值可以通過pthread_join函數(shù)的第二個參數(shù)得到。
唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個參數(shù)不是NULL,這個值將被傳遞給retval。要說明的是,一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調(diào)用pthread_join的線程則返回錯誤代碼。

####7.使用pthread_self()函數(shù)獲取當前線程的標識符:
```c
pthread_t pthread_self(void);```
返回獲得當前線程自身的標示符ID。pthread_t的類型為unsigned long int,所以在打印的時候要使用%ld方式,否則將產(chǎn)生奇怪的結(jié)果。

####8.使用pthread_equal()函數(shù)比較判斷是否是同一個線程:
```c
int pthread_equal(pthread_t tid1, pthread_t tid2);```
如果tid1和tid2相同,函數(shù)返回一個非0值,否則返回0。
如果tid1或tid2中任何一個是非法值,則返回將是不可預料的。

####9.使用pthread_cancel()取消指定線程:
```c
int pthread_cancel(pthread_t thread);```
*函數(shù)返回值:*函數(shù)成功返回0。任何其他返回值都表示錯誤。
退出一個線程。
如何響應(yīng)退出請求取決于目標線程的狀態(tài)。
當然,線程也不是被動的被別人結(jié)束。它可以通過設(shè)置自身的屬性來決定如何結(jié)束。
線程的被動結(jié)束分為兩種,一種是異步終結(jié),另外一種是同步終結(jié)。異步終結(jié)就是當其他線程調(diào)用 pthread_cancel的時候,線程就立刻被結(jié)束。而同步終結(jié)則不會立刻終結(jié),它會繼續(xù)運行,直到到達下一個結(jié)束點(cancellation point)。當一個線程被按照默認的創(chuàng)建方式創(chuàng)建,那么它的屬性是同步終結(jié)。
發(fā)送終止信號給thread線程,如果成功則返回0,否則為非0值。發(fā)送成功并不意味著thread會終止。

##二、線程并發(fā)要求 
1.同步,代碼依賴資源有前后關(guān)系(競爭),試衣間(互斥)。進程/線程中的部分指令需要按照一定 順序前后執(zhí)行。
2.競爭,有競爭才會同步。對于有限資源的共享使用過程中產(chǎn)生的競爭關(guān)系。
3.互斥,對于共享資源的操作同時只能有一個進程/線程。
4.死鎖,互相等待資源。//不應(yīng)有
5.饑餓,長時間(設(shè)置時間點)無法獲取資源。//不應(yīng)有
6.異步,完全沒有關(guān)系。不用去保護。進程/線程之間的指令執(zhí)行無順序。
[并發(fā)性:互斥和同步、死鎖和饑餓 ](http://blog.csdn.net/u013271921/article/details/45459351)
[進程同步互斥——不死鎖的哲學家問題](http://www.oschina.net/code/snippet_180916_7504)
[操作系統(tǒng)中的互斥,同步與死鎖 ](http://blog.csdn.net/t_tbread/article/details/22678549)

##三、線程間通信
信息數(shù)據(jù)交換,使用多個線程都可見的內(nèi)存區(qū)域。A產(chǎn)生的B使用。標識符

###保護機制:
#####1.線程互斥鎖: 
**互斥量(Mutex)**是“mutual exclusion”的縮寫。互斥量是實現(xiàn)線程同步,和保護同時寫共享數(shù)據(jù)的主要方法。保障有同一把鎖保護的共享資源被多個線程互斥訪問?;コ饬繉蚕頂?shù)據(jù)的保護就像一把鎖。
######互斥量必須用類型pthread_mutex_t類型聲明。
對臨界區(qū)加鎖以實現(xiàn)互斥,當某個進程進入臨界區(qū)之后,它將鎖上臨界區(qū),直到它退出臨界區(qū)為止。
>互斥鎖初始化函數(shù)
```c
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);```

***函數(shù)說明:***
    該函數(shù)用于C函數(shù)的多線程編程中,互斥鎖的初始化。
***函數(shù)參數(shù):***
函數(shù)是以動態(tài)方式創(chuàng)建互斥鎖的,第一個參數(shù) mutex 是指向要初始化的互斥鎖的指針。第二個參數(shù) attr 是指向新建互斥鎖屬性對象的指針,該屬性對象定義要初始化的互斥鎖的屬性。如果該指針為 NULL,則使用默認的快速互斥鎖屬性。
互斥鎖的屬性在創(chuàng)建鎖的時候指定,在LinuxThreads實現(xiàn)中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經(jīng)被鎖定的互斥鎖加鎖時表現(xiàn)不同。
***函數(shù)返回值:***執(zhí)行成功完成之后會返回0,其他任何返回值都表示出現(xiàn)了錯誤。
函數(shù)成功執(zhí)行后,互斥鎖被初始化為鎖住態(tài)。
[線程同步:互斥量,死鎖](http://blog.csdn.net/tototuzuoquan/article/details/39553761)

>互斥鎖的獲取(加鎖)
```c
 int pthread_mutex_lock(pthread_mutex_t *mutex);```
互斥鎖的釋放(解鎖)
```c
 int pthread_mutex_unlock(pthread_mutex_t *mutex);```
以上兩個不可能被兩個不同的線程同時得到,而必須等待解鎖。
在同一進程中的線程,如果加鎖后沒有解鎖,則任何其他線程都無法再獲得鎖。
pthread_mutex_lock()聲明開始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock()為止,均被上鎖,即同一時間只能被一個線程調(diào)用執(zhí)行。當一個線程執(zhí)行到pthread_mutex_lock()處時,如果該鎖此時被另一個線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋€線程釋放此互斥鎖。

多個線程對資源改寫都要互斥(保護)
***提示:***總體來講, 有幾個不成文的基本原則:
- 對共享資源操作前一定要獲得鎖。
- 完成操作以后一定要釋放鎖。
- 盡量短時間地占用鎖。
- 如果有多鎖, 如獲得順序是ABC連環(huán)扣, 釋放順序也應(yīng)該是ABC。
- 線程錯誤返回時應(yīng)該釋放它所獲得的鎖。

[pthreads線程(二) 線程同步--互斥量/鎖](http://www.cnblogs.com/dongsheng/p/4186358.html)

####2.線程信號量:解決多個線程在使用共享有限資源的同步問題。
線程的信號量與進程間通信中使用的信號量的概念是一樣,它是一種特殊的變量,它可以被增加或減少,但對其的關(guān)鍵訪問被保證是原子操作。如果一個程序中有多個線程試圖改變一個信號量的值,系統(tǒng)將保證所有的操作都將依次進行。
而只有0和1兩種取值的信號量叫做二進制信號量,在這里將重點介紹。而信號量一般常用于保護一段代碼,使其每次只被一個執(zhí)行線程運行。我們可以使用二進制信號量來完成這個工作。

信號量是一個計數(shù)器,用于控制訪問有限共享資源的線程數(shù)。
在操作系統(tǒng)中,信號量是一整數(shù),在sem大于等于零時代表可供并發(fā)進程使用的資源實體數(shù),但sem小于零時表示正在等待使用臨界區(qū)的進程數(shù);
線程信號量,sem_t


>信號量的創(chuàng)建和初始化:
```c
#include <semaphore.h>
int sem_init (sem_t *sem, int pshared, unsigned int value);```
該函數(shù)初始化由sem指向的信號對象,設(shè)置它的共享選項,并給它一個初始的整數(shù)值。pshared控制信號量的類型,如果其值為0,就表示這個信號量是當前進程的局部信號量,否則信號量就可以在多個進程之間共享,value為sem的初始值。調(diào)用成功時返回0,失敗返回-1。

>信號量的獲取,以原子操作的方式將信號量的值--1。
```c
int sem_wait(sem_t * sem);```
sem指向的對象是由sem_init調(diào)用初始化的信號量。調(diào)用成功時返回0,失敗返回-1。

>信號量的釋放,以原子操作的方式將信號量的值++1。
```c
int sem_post(sem_t * sem);```
與sem_wait一樣,sem指向的對象是由sem_init調(diào)用初始化的信號量。調(diào)用成功時返回0,失敗返回-1。

>信號量的銷毀,用于對用完的信號量的清理。
```c
int sem_destroy (sem_t *sem);```
成功時返回0,失敗時返回-1。

[Linux多線程——使用信號量同步線程 ](http://blog.csdn.net/ljianhui/article/details/10813469/)
[線程同步(互斥鎖與信號量的作用與區(qū)別)](http://blog.csdn.net/tietao/article/details/7367827)

####3.原子操作
暨如果兩個線程企圖同時給一個信號量++1或--1,它們之間不會互相干擾。是最安全的操作,用鎖鎖在一起,或全執(zhí)行,或全擱置。


#####課程使用各參數(shù)(暫時)安排

    創(chuàng)建線程:
    #include <pthread.h>
    //全局變量,可以是結(jié)構(gòu)體,整型變量,等等。
    void *thread_func(void *arg);//聲明線程函數(shù)
    //主調(diào)函數(shù)內(nèi):
    pthread_t thread_id;    //注冊定義線程id
    pthread_create(&thread_id, NULL, thread_func, NULL); //創(chuàng)建線程并指定線程的執(zhí)行函數(shù),最后一個參數(shù)為傳到線程函數(shù)的參數(shù)。
     pthread_join(thread_id, NULL); // 等待指定線程的退出
     //自定義線程函數(shù)內(nèi):
     pthread_exit(NULL);//返回到j(luò)oin后一個參數(shù)。


    線程互斥鎖:
            pthread_mutex_t mutex;//全局定義線程互斥鎖
            主調(diào)函數(shù)內(nèi):  
            注冊定義線程id
            pthread_mutex_init(&mutex, NULL);//初始化線程互斥鎖
            創(chuàng)建線程
            等待線程退出
            自定義線程函數(shù)內(nèi):
            pthread_mutex_lock(&mutex);// 獲取互斥鎖
            pthread_mutex_unlock(&mutex); // 釋放互斥鎖
            ?。』コ怄i的獲取和釋放必須成對出現(xiàn)??!
    
    信號量:
            sem_t sem;//全局定義信號量
            void sig_handler(int signo);//聲明信號處理函數(shù)
            主調(diào)函數(shù)內(nèi):  
            注冊定義線程id
            signal(SIGINT, sig_handler);//注冊信號處理函數(shù)
            sem_init(&sem, 0, 0);// 初始化信號量為0
            創(chuàng)建線程
            等待線程退出
            sem_destroy(&sem);// 銷毀信號量
            自定義線程函數(shù)內(nèi):
            while(1)
            sem_wait(&sem);// sem -1 對信號量進行wait(即-1)操作,如果<0,阻塞。
            自定義信號處理函數(shù)內(nèi):
            sem_post(&sem);//sem +1 對信號量進行post(即+1)操作,如果<=0,系統(tǒng)喚醒一個等待線程。
[進程同步互斥——不死鎖的哲學家問題](http://www.oschina.net/code/snippet_180916_7504)
[競爭與同步,互斥量,信號量,死鎖,條件變量,哲學家吃飯問題](http://www.th7.cn/system/lin/201509/134476.shtml)
####參考資料
[對Linux中多線程編程中pthread_join的理解](http://www.linuxidc.com/Linux/2013-09/89931.htm)
[Linux 線程操作函數(shù)技能總結(jié)](http://blog.csdn.net/shaderdx/article/details/50475982)
[pthread多線程編程的學習小結(jié)](https://www.oschina.net/question/234345_40365)
[pThreads線程(一) 基本API](http://www.cnblogs.com/dongsheng/p/4184153.html)
[Linux Pthread 深入解析](http://blog.csdn.net/u010009623/article/details/53116814)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay閱讀 1,724評論 0 52
  • 簡介 線程創(chuàng)建 線程屬性設(shè)置 線程參數(shù)傳遞 線程優(yōu)先級 線程的數(shù)據(jù)處理 線程的分離狀態(tài) 互斥鎖 信號量 一 線程創(chuàng)...
    第八區(qū)閱讀 8,713評論 1 6
  • 系統(tǒng)與網(wǎng)絡(luò)編程 小作業(yè) 公交車停發(fā)車程序 線程 并發(fā)執(zhí)行:看起來像同時運行,實際上在單核cpu里只有一個。將其排成...
    I踏雪尋梅閱讀 510評論 0 3
  • 線程基礎(chǔ) 線程是進程的一個執(zhí)行單元,執(zhí)行一段程序片段,線程共享全局變量;線程的查看可以使用命令或者文件來進行查看;...
    秋風弄影閱讀 806評論 0 0
  • 摘要 線程概念,線程與進程的區(qū)別與聯(lián)系學會線程控制,線程創(chuàng)建,線程終止,線程等待了解線程分離與線程安全學會線程同步...
    狼之足跡閱讀 532評論 2 3

友情鏈接更多精彩內(nèi)容