Linux多線程

進(jìn)程與線程

  典型的UNIX/Linux進(jìn)程可以看成只有一個(gè)控制線程:一個(gè)進(jìn)程在同一時(shí)刻只做一件事情。有了多個(gè)控制線程后,在程序設(shè)計(jì)時(shí)可以把進(jìn)程設(shè)計(jì)成在同一時(shí)刻做不止一件事,每個(gè)線程各自處理獨(dú)立的任務(wù)。

  進(jìn)程是程序執(zhí)行時(shí)的一個(gè)實(shí)例,是擔(dān)當(dāng)分配系統(tǒng)資源(CPU時(shí)間、內(nèi)存等)的基本單位。在面向線程設(shè)計(jì)的系統(tǒng)中,進(jìn)程本身不是基本運(yùn)行單位,而是線程的容器。程序本身只是指令、數(shù)據(jù)及其組織形式的描述,進(jìn)程才是程序(那些指令和數(shù)據(jù))的真正運(yùn)行實(shí)例。

線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。線程包含了表示進(jìn)程內(nèi)執(zhí)行環(huán)境必須的信息,其中包括進(jìn)程中表示線程的線程ID、一組寄存器值、棧、調(diào)度優(yōu)先級(jí)和策略、信號(hào)屏蔽字、errno常量以及線程私有數(shù)據(jù)。進(jìn)程的所有信息對(duì)該進(jìn)程的所有線程都是共享的,包括可執(zhí)行的程序文本、程序的全局內(nèi)存和堆內(nèi)存、棧以及文件描述符。在Unix和類Unix操作系統(tǒng)中線程也被稱為輕量級(jí)進(jìn)程(lightweight processes),但輕量級(jí)進(jìn)程更多指的是內(nèi)核線程(kernel thread),而把用戶線程(user thread)稱為線程。

"進(jìn)程——資源分配的最小單位,線程——程序執(zhí)行的最小單位"

  進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其它進(jìn)程產(chǎn)生影響,而線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程沒(méi)有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。但對(duì)于一些要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程,不能用進(jìn)程。

從函數(shù)調(diào)用上來(lái)說(shuō),進(jìn)程創(chuàng)建使用fork()操作;線程創(chuàng)建使用clone()操作。Richard Stevens大師這樣說(shuō)過(guò):

forkis expensive. Memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. Current implementations use a technique calledcopy-on-write, which avoids a copy of the parent's data space to the child until the child needs its own copy. But, regardless of this optimization,forkis expensive.

IPC is required to pass information between the parent and childafter?thefork. Passing information from the parent to the childbefore?theforkis easy, since the child starts with a copy of the parent's data space and with a copy of all the parent's descriptors. But, returning information from the child to the parent takes more work.

Threads help with both problems. Threads are sometimes calledlightweight processes?since a thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than process creation.

All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem ofsynchronization.

使用線程的理由

(本部分摘自Linux多線程編程(不限Linux)

從上面我們知道了進(jìn)程與線程的區(qū)別,其實(shí)這些區(qū)別也就是我們使用線程的理由??偟膩?lái)說(shuō)就是:進(jìn)程有獨(dú)立的地址空間,線程沒(méi)有單獨(dú)的地址空間(同一進(jìn)程內(nèi)的線程共享進(jìn)程的地址空間)。

使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來(lái)維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。據(jù)統(tǒng)計(jì),總的說(shuō)來(lái),一個(gè)進(jìn)程的開(kāi)銷大約是一個(gè)線程開(kāi)銷的30倍左右,當(dāng)然,在具體的系統(tǒng)上,這個(gè)數(shù)據(jù)可能會(huì)有較大的區(qū)別。

使用多線程的理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來(lái)說(shuō),它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過(guò)通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí),而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來(lái)其他一些問(wèn)題,有的變量不能同時(shí)被兩個(gè)線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來(lái)災(zāi)難性的打擊,這些正是編寫多線程程序時(shí)最需要注意的地方。

  除了以上所說(shuō)的優(yōu)點(diǎn)外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點(diǎn):

提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長(zhǎng)時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長(zhǎng)的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。

使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。

改善程序結(jié)構(gòu)。一個(gè)既長(zhǎng)又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。

Linux上線程開(kāi)發(fā)API概要

  多線程開(kāi)發(fā)在 Linux 平臺(tái)上已經(jīng)有成熟的 pthread 庫(kù)支持。其涉及的多線程開(kāi)發(fā)的最基本概念主要包含三點(diǎn):線程,互斥鎖,條件。其中,線程操作又分線程的創(chuàng)建,退出,等待 3 種?;コ怄i則包括 4 種操作,分別是創(chuàng)建,銷毀,加鎖和解鎖。條件操作有 5 種操作:創(chuàng)建,銷毀,觸發(fā),廣播和等待。其他的一些線程擴(kuò)展概念,如信號(hào)燈等,都可以通過(guò)上面的三個(gè)基本元素的基本操作封裝出來(lái)。詳細(xì)請(qǐng)見(jiàn)下表:

與線程自身相關(guān)API

1. 線程創(chuàng)建

#include intpthread_create(pthread_t *restrict tidp,constpthread_attr_t *restrict attr,void*(*start_rtn)(void*),void*restrict arg);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

當(dāng)pthread_create成功返回時(shí),由tidp指向的內(nèi)存單元被設(shè)置為新創(chuàng)建線程的線程ID。attr參數(shù)用于定制各種不同的線程屬性,暫可以把它設(shè)置為NULL,以創(chuàng)建默認(rèn)屬性的線程。

  新創(chuàng)建的線程從start_rtn函數(shù)的地址開(kāi)始運(yùn)行,該函數(shù)只有一個(gè)無(wú)類型指針參數(shù)arg。如果需要向start_rtn函數(shù)傳遞的參數(shù)不止一個(gè),那么需要把這些參數(shù)放到一個(gè)結(jié)構(gòu)中,然后把這個(gè)結(jié)構(gòu)的地址作為arg參數(shù)傳入。

2. 線程退出

  單個(gè)線程可以通過(guò)以下三種方式退出,在不終止整個(gè)進(jìn)程的情況下停止它的控制流:

  1)線程只是從啟動(dòng)例程中返回,返回值是線程的退出碼。

  2)線程可以被同一進(jìn)程中的其他線程取消。

  3)線程調(diào)用pthread_exit:

#include intpthread_exit(void*rval_ptr);

  rval_ptr是一個(gè)無(wú)類型指針,與傳給啟動(dòng)例程的單個(gè)參數(shù)類似。進(jìn)程中的其他線程可以通過(guò)調(diào)用pthread_join函數(shù)訪問(wèn)到這個(gè)指針。

3. 線程等待

#include intpthread_join(pthread_t thread,void**rval_ptr);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  調(diào)用這個(gè)函數(shù)的線程將一直阻塞,直到指定的線程調(diào)用pthread_exit、從啟動(dòng)例程中返回或者被取消。如果例程只是從它的啟動(dòng)例程返回i,rval_ptr將包含返回碼。如果線程被取消,由rval_ptr指定的內(nèi)存單元就置為PTHREAD_CANCELED。

  可以通過(guò)調(diào)用pthread_join自動(dòng)把線程置于分離狀態(tài),這樣資源就可以恢復(fù)。如果線程已經(jīng)處于分離狀態(tài),pthread_join調(diào)用就會(huì)失敗,返回EINVAL。

  如果對(duì)線程的返回值不感興趣,可以把rval_ptr置為NULL。在這種情況下,調(diào)用pthread_join函數(shù)將等待指定的線程終止,但并不獲得線程的終止?fàn)顟B(tài)。

4. 線程脫離

  一個(gè)線程或者是可匯合(joinable,默認(rèn)值),或者是脫離的(detached)。當(dāng)一個(gè)可匯合的線程終止時(shí),它的線程ID和退出狀態(tài)將留存到另一個(gè)線程對(duì)它調(diào)用pthread_join。脫離的線程卻像守護(hù)進(jìn)程,當(dāng)它們終止時(shí),所有相關(guān)的資源都被釋放,我們不能等待它們終止。如果一個(gè)線程需要知道另一線程什么時(shí)候終止,那就最好保持第二個(gè)線程的可匯合狀態(tài)。

  pthread_detach函數(shù)把指定的線程轉(zhuǎn)變?yōu)槊撾x狀態(tài)。

#include int pthread_detach(pthread_t thread);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  本函數(shù)通常由想讓自己脫離的線程使用,就如以下語(yǔ)句:

pthread_detach(pthread_self());

5. 線程ID獲取及比較

#include pthread_t pthread_self(void);// 返回:調(diào)用線程的ID

  對(duì)于線程ID比較,為了可移植操作,我們不能簡(jiǎn)單地把線程ID當(dāng)作整數(shù)來(lái)處理,因?yàn)椴煌到y(tǒng)對(duì)線程ID的定義可能不一樣。我們應(yīng)該要用下邊的函數(shù):

#include int pthread_equal(pthread_t tid1, pthread_t tid2);// 返回:若相等則返回非0值,否則返回0

對(duì)于多線程程序來(lái)說(shuō),我們往往需要對(duì)這些多線程進(jìn)行同步。同步(synchronization)是指在一定的時(shí)間內(nèi)只允許某一個(gè)線程訪問(wèn)某個(gè)資源。而在此時(shí)間內(nèi),不允許其它的線程訪問(wèn)該資源。我們可以通過(guò)互斥鎖(mutex),條件變量(condition variable)和讀寫鎖(reader-writer lock)來(lái)同步資源。在這里,我們暫不介紹讀寫鎖。

與互斥鎖相關(guān)API

  互斥量(mutex)從本質(zhì)上來(lái)說(shuō)是一把鎖,在訪問(wèn)共享資源前對(duì)互斥量進(jìn)行加鎖,在訪問(wèn)完成后釋放互斥量上的鎖。對(duì)互斥量進(jìn)行加鎖后,任何其他試圖再次對(duì)互斥量加鎖的線程將會(huì)被阻塞直到當(dāng)前線程釋放該互斥鎖。如果釋放互斥鎖時(shí)有多個(gè)線程阻塞,所有在該互斥鎖上的阻塞線程都會(huì)變成可運(yùn)行狀態(tài),第一個(gè)變?yōu)榭蛇\(yùn)行狀態(tài)的線程可以對(duì)互斥量加鎖,其他線程將會(huì)看到互斥鎖依然被鎖住,只能回去等待它重新變?yōu)榭捎谩T谶@種方式下,每次只有一個(gè)線程可以向前運(yùn)行。

  在設(shè)計(jì)時(shí)需要規(guī)定所有的線程必須遵守相同的數(shù)據(jù)訪問(wèn)規(guī)則。只有這樣,互斥機(jī)制才能正常工作。操作系統(tǒng)并不會(huì)做數(shù)據(jù)訪問(wèn)的串行化。如果允許其中的某個(gè)線程在沒(méi)有得到鎖的情況下也可以訪問(wèn)共享資源,那么即使其它的線程在使用共享資源前都獲取了鎖,也還是會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。

  互斥變量用pthread_mutex_t數(shù)據(jù)類型表示。在使用互斥變量前必須對(duì)它進(jìn)行初始化,可以把它置為常量PTHREAD_MUTEX_INITIALIZER(只對(duì)靜態(tài)分配的互斥量),也可以通過(guò)調(diào)用pthread_mutex_init函數(shù)進(jìn)行初始化。如果動(dòng)態(tài)地分配互斥量(例如通過(guò)調(diào)用malloc函數(shù)),那么在釋放內(nèi)存前需要調(diào)用pthread_mutex_destroy。

1. 創(chuàng)建及銷毀互斥鎖

#include intpthread_mutex_init(pthread_mutex_t *restrict mutex,constpthread_mutexattr_t *restrict attr);int pthread_mutex_destroy(pthread_mutex_t mutex);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  要用默認(rèn)的屬性初始化互斥量,只需把a(bǔ)ttr設(shè)置為NULL。

2. 加鎖及解鎖

#include int pthread_mutex_lock(pthread_mutex_t mutex);int pthread_mutex_trylock(pthread_mutex_t mutex);int pthread_mutex_unlock(pthread_mutex_t mutex);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  如果線程不希望被阻塞,它可以使用pthread_mutex_trylock嘗試對(duì)互斥量進(jìn)行加鎖。如果調(diào)用pthread_mutex_trylock時(shí)互斥量處于未鎖住狀態(tài),那么pthread_mutex_trylock將鎖住互斥量,不會(huì)出現(xiàn)阻塞并返回0,否則pthread_mutex_trylock就會(huì)失敗,不能鎖住互斥量,而返回EBUSY。

與條件變量相關(guān)API

?  ?條件變量是線程另一可用的同步機(jī)制。條件變量給多個(gè)線程提供了一個(gè)會(huì)合的場(chǎng)所。條件變量與互斥量一起使用時(shí),允許線程以無(wú)競(jìng)爭(zhēng)的方式等待特定的條件發(fā)生。

  條件本身是由互斥量保護(hù)的。線程在改變條件狀態(tài)前必須首先鎖住互斥量,其他線程在獲得互斥量之前不會(huì)察覺(jué)到這種改變,因?yàn)楸仨氭i定互斥量以后才能計(jì)算條件。

  條件變量使用之前必須首先初始化,pthread_cond_t數(shù)據(jù)類型代表的條件變量可以用兩種方式進(jìn)行初始化,可以把常量PTHREAD_COND_INITIALIZER賦給靜態(tài)分配的條件變量,但是如果條件變量是動(dòng)態(tài)分配的,可以使用pthread_cond_destroy函數(shù)對(duì)條件變量進(jìn)行去除初始化(deinitialize)。

1. 創(chuàng)建及銷毀條件變量

#include intpthread_cond_init(pthread_cond_t *restrict cond,constpthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t cond);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  除非需要?jiǎng)?chuàng)建一個(gè)非默認(rèn)屬性的條件變量,否則pthread_cont_init函數(shù)的attr參數(shù)可以設(shè)置為NULL。

2. 等待

#include intpthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);intpthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, condstructtimespec *restrict timeout);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  pthread_cond_wait等待條件變?yōu)檎妗H绻诮o定的時(shí)間內(nèi)條件不能滿足,那么會(huì)生成一個(gè)代表一個(gè)出錯(cuò)碼的返回變量。傳遞給pthread_cond_wait的互斥量對(duì)條件進(jìn)行保護(hù),調(diào)用者把鎖住的互斥量傳給函數(shù)。函數(shù)把調(diào)用線程放到等待條件的線程列表上,然后對(duì)互斥量解鎖,這兩個(gè)操作都是原子操作。這樣就關(guān)閉了條件檢查和線程進(jìn)入休眠狀態(tài)等待條件改變這兩個(gè)操作之間的時(shí)間通道,這樣線程就不會(huì)錯(cuò)過(guò)條件的任何變化。pthread_cond_wait返回時(shí),互斥量再次被鎖住。

  pthread_cond_timedwait函數(shù)的工作方式與pthread_cond_wait函數(shù)類似,只是多了一個(gè)timeout。timeout指定了等待的時(shí)間,它是通過(guò)timespec結(jié)構(gòu)指定。

3. 觸發(fā)

#include int pthread_cond_signal(pthread_cond_t cond);int pthread_cond_broadcast(pthread_cond_t cond);// 返回:若成功返回0,否則返回錯(cuò)誤編號(hào)

  這兩個(gè)函數(shù)可以用于通知線程條件已經(jīng)滿足。pthread_cond_signal函數(shù)將喚醒等待該條件的某個(gè)線程,而pthread_cond_broadcast函數(shù)將喚醒等待該條件的所有進(jìn)程。

  注意一定要在改變條件狀態(tài)以后再給線程發(fā)信號(hào)。

示例

示例1:創(chuàng)建一個(gè)簡(jiǎn)單的線程

  下邊程序中創(chuàng)建了一個(gè)簡(jiǎn)單的線程thread1,負(fù)責(zé)將全局變量g_Flag重置為1:

1#include 2#include 3#include 4#include 5#include 6 7#defineunsigned int unit; 8 9intg_Flag =0;10void*thread1(void*);1112intmain(intargc,char**argv)13{14printf("Enter main function.\n");1516? ? pthread_t tid1;17interr1 =0;1819err1 = pthread_create(&tid1, NULL, thread1, NULL);20if(err1 !=0)21printf("%s: %d\n", __func__, strerror(err1));2223printf("This is main the thread, process ID is %u, thread ID is %u, and g_Flag is %d.\n", (uint)getpid(), (uint)pthread_self, g_Flag);2425printf("Leave main functiona.\n");26exit(0);27}2829void*thread1(void*arg)30{31printf("Enter thread1.\n");32printf("This is thread1, process ID is %u, thread ID is %u, and g_Flag is %d.\n", (uint)getpid(), (uint)pthread_self, g_Flag);33g_Flag =1;34printf("This is thread1, process ID is %u, thread ID is %u, and g_Flag is %d.\n", (uint)getpid(), (uint)pthread_self, g_Flag);35printf("Leave thread1.\n");36pthread_exit(0);37}

  程序運(yùn)行結(jié)果如下:

  很明顯,該程序的進(jìn)程并沒(méi)有執(zhí)行線程thread1。

具體原因是主線程與新線程存在競(jìng)爭(zhēng):主線程需要休眠,如果主線程不休眠,它就可能退出,這樣在新線程有機(jī)會(huì)運(yùn)行之前整個(gè)進(jìn)程就已經(jīng)終止了。這種行為依賴于操作系統(tǒng)的線程實(shí)現(xiàn)方法和調(diào)度方法。

這個(gè)程序還有一點(diǎn)值得注意的,就是新線程thread1是通過(guò)調(diào)用pthread_self函數(shù)獲取自己的線程ID,而不是從共享內(nèi)存中讀出或者從線程的啟動(dòng)例程(pthread_create(...))中以參數(shù)(tid)的形式接收到。在上邊的程序中,主線程把新線程ID存放在tid1中,但是新建的線程并不能安全地使用它。如果新線程在主線程調(diào)用pthread_create返回之前就運(yùn)行了,那么新線程看到的是未經(jīng)初始化的tid1的內(nèi)容,這個(gè)內(nèi)容并不是正確的線程ID。

示例2:改進(jìn)示例1

?  這一次,我們?cè)谑纠?程序的第25、26行之間插入

1sleep(1);

?  程序輸出如下:

  這一次,很明顯線程1已被執(zhí)行。主線程和線程1的進(jìn)程ID號(hào)一樣是沒(méi)問(wèn)題的,關(guān)鍵是線程號(hào)還一樣(新線程中輸出的線程ID是主線程的),這說(shuō)明主線程跟新線程還是存在競(jìng)爭(zhēng)。

示例3:改進(jìn)示例2?

  示例3程序如下:

1#include 2#include 3#include 4#include 5#include 6 7#defineunsigned int unit; 8 9intg_Flag =0;10voidprintids(constchar*s);11void*thread1(void*);1213intmain(intargc,char**argv)14{15printf("Enter main function.\n");1617? ? pthread_t tid1;18interr1 =0;1920err1 = pthread_create(&tid1, NULL, thread1, NULL);21if(err1 !=0)22printf("%s: %d\n", __func__, strerror(err1));2324printids("This is the main thread");? ? 25printf("Leave main function.\n");26sleep(1);27exit(0);28}2930voidprintids(constchar*s)31{32? ? pid_t? ? ? ? pid;33? ? pthread_t? ? tid;3435pid = getpid();36tid = pthread_self();37printf("%s, process ID is %u, thread ID is %u, and g_Flag is %d.\n", s, (uint)pid, (uint)tid, g_Flag);38}3940void*thread1(void*arg)41{42printf("Enter thread1.\n");43printids("This is thread1");44g_Flag =1;45printids("This is thread1");46printf("Leave thread1.\n");47pthread_exit(0);48}

?  程序輸出如下:

  這下程序就正確了。

示例4:比示例3更通用的方式

  在這里我們采用另一種比示例3更通用的方式。我們貼出與示例3程序不同的部分:

1intmain(intargc,char**argv) 2{ 3printf("Enter main function.\n"); 4 5? ? pthread_t tid1; 6interr1 =0; 7void*rval1; 8 9err1 = pthread_create(&tid1, NULL, thread1, NULL);10if(err1 !=0)11printf("%s: %d\n", __func__, strerror(err1));1213printids("This is the main thread");? ? 1415err1 = pthread_join(tid1, &rval1);16if(err1 !=0)17printf("%s: %d\n", __func__, strerror(err1));18printf("thread1 exit code is %d.\n", (int)rval1);1920printf("Leave main function.\n");21exit(0);22}

?  程序輸出如下:

  這下程序也是正確的。不過(guò)調(diào)用這個(gè)pthread_join函數(shù)的線程將一直阻塞,直到指定的線程調(diào)用pthread_exit、從啟動(dòng)例程中返回或者被取消。

示例5:比示例4稍微復(fù)雜的例子

?  示例5程序如下:

?View Code

  程序運(yùn)行結(jié)果如下:

  很明顯,線程2和線程3存在競(jìng)爭(zhēng),導(dǎo)致線程2的輸出存在問(wèn)題。

示例6:改進(jìn)示例5

  為了防止線程同時(shí)更改同一數(shù)據(jù),我們需要給每個(gè)線程加鎖,具體程序如下:

?View Code

  程序輸出如下:

  這下程序輸出是正確的。想要更改線程的執(zhí)行順序,我們可以將pthread_join放在恰當(dāng)?shù)牡胤健A硗?,我們的鎖主要是要保護(hù)數(shù)據(jù)操作的安全性,而不是printf函數(shù)輸出的正確性。不過(guò)我們?cè)诔绦蛑袑rintf函數(shù)同時(shí)保護(hù)起來(lái),這樣保證每個(gè)線程的輸出完整性。

示例7:運(yùn)用條件變量?

  示例7程序?qū)崿F(xiàn)的功能是當(dāng)全局變量g_Flag從2變?yōu)?或從1變?yōu)?時(shí),主線程退出。具體程序(存在競(jìng)爭(zhēng))如下:

?View Code

  程序輸出如下:

  或是

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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