POSIX 多線程程序設(shè)計
Blaise Barney, Lawrence Livermore National Laboratory??
目錄表?
摘要??譯者序Pthreads 概述?? 什么是線程? ?什么是Pthreads? ?為什么使用Pthreads???使用線程設(shè)計程序 ?Pthreads API編譯多線程程序??線程管理 ? 創(chuàng)建和終止線程??向線程傳遞參數(shù)??連接(Joining)和分離( Detaching)線程 ?棧管理 ?其它函數(shù)??互斥量(Mutex Variables)?? 互斥量概述??創(chuàng)建和銷毀互斥量 ?鎖定(Locking)和解鎖(Unlocking)互斥量??條件變量(Condition Variable)?? 條件變量概述?創(chuàng)建和銷毀條件變量 ?等待(Waiting)和發(fā)送信號(Signaling)??沒有覆蓋的主題 ?Pthread 庫API參考??參考資料??
摘要??
在多處理器共享內(nèi)存的架構(gòu)中(如:對稱多處理系統(tǒng)SMP),線程可以用于實現(xiàn)程序的并行性。歷史上硬件銷售商實現(xiàn)了各種私有版本的多線程庫,使得軟件開發(fā)者不得不關(guān)心它的移植性。對于UNIX系統(tǒng),IEEE POSIX 1003.1標(biāo)準(zhǔn)定義了一個C語言多線程編程接口。依附于該標(biāo)準(zhǔn)的實現(xiàn)被稱為POSIX theads?或?Pthreads。?
該教程介紹了Pthreads的概念、動機和設(shè)計思想。內(nèi)容包含了Pthreads API主要的三大類函數(shù):線程管理(Thread Managment)、互斥量(Mutex Variables)和條件變量(Condition Variables)。向剛開始學(xué)習(xí)Pthreads的程序員提供了演示例程。?
適于:剛開始學(xué)習(xí)使用線程實現(xiàn)并行程序設(shè)計;對于C并行程序設(shè)計有基本了解。不熟悉并行程序設(shè)計的可以參考EC3500: Introduction To Parallel Computing。
?
Pthreads
概述?
什么是線程??
?
技術(shù)上,線程可以定義為:可以被操作系統(tǒng)調(diào)度的獨立的指令流。但是這是什么意思呢??對于軟件開發(fā)者,在主程序中運行的“函數(shù)過程”可以很好的描述線程的概念。?進一步,想象下主程序(a.out)包含了許多函數(shù),操作系統(tǒng)可以調(diào)度這些函數(shù),使之同時或者(和)獨立的執(zhí)行。這就描述了“多線程”程序。?怎樣完成的呢??
?
在理解線程之前,應(yīng)先對UNIX進程(process)有所了解。進程被操作系統(tǒng)創(chuàng)建,需要相當(dāng)多的“額外開銷”。進程包含了程序的資源和執(zhí)行狀態(tài)信息。如下:? 進程ID,進程group ID,用戶ID和group ID?環(huán)境?工作目錄??程序指令?寄存器?棧?堆?文件描述符?信號動作(Signal actions)?共享庫?進程間通信工具(如:消息隊列,管道,信號量或共享內(nèi)存)?
???
?
UNIX PROCESS?
THREADS WITHIN A UNIX PROCESS?
線程使用并存在于進程資源中,還可以被操作系統(tǒng)調(diào)用并獨立地運行,這主要是因為線程僅僅復(fù)制必要的資源以使自己得以存在并執(zhí)行。?獨立的控制流得以實現(xiàn)是因為線程維持著自己的:? 堆棧指針?寄存器?調(diào)度屬性(如:策略或優(yōu)先級)?待定的和阻塞的信號集合(Set of pending and blocked signals)?線程專用數(shù)據(jù)(TSD:Thread Specific Data.)?因此,在UNIX環(huán)境下線程:? 存在于進程,使用進程資源?擁有自己獨立的控制流,只要父進程存在并且操作系統(tǒng)支持?只復(fù)制必可以使得獨立調(diào)度的必要資源?可以和其他線程獨立(或非獨立的)地共享進程資源?當(dāng)父進程結(jié)束時結(jié)束,或者相關(guān)類似的?是“輕型的”,因為大部分額外開銷已經(jīng)在進程創(chuàng)建時完成了?因為在同一個進程中的線程共享資源:? 一個線程對系統(tǒng)資源(如關(guān)閉一個文件)的改變對所有其它線程是可以見的?兩個同樣值的指針指向相同的數(shù)據(jù)?讀寫同一個內(nèi)存位置是可能的,因此需要成員顯式地使用同步?
?
Pthreads?概述?
什么是?Pthreads??
歷史上,硬件銷售商實現(xiàn)了私有版本的多線程庫。這些實現(xiàn)在本質(zhì)上各自不同,使得程序員難于開發(fā)可移植的應(yīng)用程序。?為了使用線程所提供的強大優(yōu)點,需要一個標(biāo)準(zhǔn)的程序接口。對于UNIX系統(tǒng),IEEE POSIX 1003.1c(1995)標(biāo)準(zhǔn)制訂了這一標(biāo)準(zhǔn)接口。依賴于該標(biāo)準(zhǔn)的實現(xiàn)就稱為POSIX threads?或者Pthreads。現(xiàn)在多數(shù)硬件銷售商也提供Pthreads,附加于私有的API。?Pthreads?被定義為一些C語言類型和函數(shù)調(diào)用,用pthread.h頭(包含)文件和線程庫實現(xiàn)。這個庫可以是其它庫的一部分,如libc。?
?
Pthreads?概述?
為什么使用?Pthreads??
使用Pthreads的主要動機是提高潛在程序的性能。?當(dāng)與創(chuàng)建和管理進程的花費相比,線程可以使用操作系統(tǒng)較少的開銷,管理線程需要較少的系統(tǒng)資源。?
例如,下表比較了fork()函數(shù)和pthread_create()函數(shù)所用的時間。計時反應(yīng)了50,000個進程/線程的創(chuàng)建,使用時間工具實現(xiàn),單位是秒,沒有優(yōu)化標(biāo)志。?
備注:不要期待系統(tǒng)和用戶時間加起來就是真實時間,因為這些SMP系統(tǒng)有多個CPU同時工作。這些都是近似值。?
平臺?
fork()?
pthread_create()?
real?
user?
sys?
real?
user?
sys?
AMD 2.4 GHz Opteron (8cpus/node)??
41.07?
60.08?
9.01?
0.66?
0.19?
0.43?
IBM 1.9 GHz POWER5 p5-575 (8cpus/node)??
64.24?
30.78?
27.68?
1.75?
0.69?
1.10?
IBM 1.5 GHz POWER4 (8cpus/node)??
104.05?
48.64?
47.21?
2.01?
1.00?
1.52?
INTEL 2.4 GHz Xeon (2 cpus/node)??
54.95?
1.54?
20.78?
1.64?
0.67?
0.90?
INTEL 1.4 GHz Itanium2 (4 cpus/node)??
54.54?
1.07?
22.22?
2.03?
1.26?
0.67?
fork_vs_thread.txt??
在同一個進程中的所有線程共享同樣的地址空間。較于進程間的通信,在許多情況下線程間的通信效率比較高,且易于使用。?較于沒有使用線程的程序,使用線程的應(yīng)用程序有潛在的性能增益和實際的優(yōu)點:? CPU使用I/O交疊工作:例如,一個程序可能有一個需要較長時間的I/O操作,當(dāng)一個線程等待I/O系統(tǒng)調(diào)用完成時,CPU可以被其它線程使用。?優(yōu)先/實時調(diào)度:比較重要的任務(wù)可以被調(diào)度,替換或者中斷較低優(yōu)先級的任務(wù)。?異步事件處理:頻率和持續(xù)時間不確定的任務(wù)可以交錯。例如,web服務(wù)器可以同時為前一個請求傳輸數(shù)據(jù)和管理新請求。?考慮在SMP架構(gòu)上使用Pthreads的主要動機是獲的最優(yōu)的性能。特別的,如果一個程序使用MPI在節(jié)點通信,使用Pthreads可以使得節(jié)點數(shù)據(jù)傳輸?shù)玫斤@著提高。?例如:? MPI庫經(jīng)常用共享內(nèi)存實現(xiàn)節(jié)點任務(wù)通信,這至少需要一次內(nèi)存復(fù)制操作(進程到進程)。?Pthreads沒有中間的內(nèi)存復(fù)制,因為線程和一個進程共享同樣的地址空間。沒有數(shù)據(jù)傳輸。變成cache-to-CPU或memory-to-CPU的帶寬(最壞情況),速度是相當(dāng)?shù)目臁?比較如下:?
Platform?
MPI Shared Memory Bandwidth
(GB/sec)?
Pthreads Worst Case
Memory-to-CPU Bandwidth?
(GB/sec)?
AMD 2.4 GHz Opteron ?
1.2?
5.3?
IBM 1.9 GHz POWER5 p5-575 ?
4.1?
16?
IBM 1.5 GHz POWER4 ?
2.1?
4?
Intel 1.4 GHz Xeon ?
0.3?
4.3?
Intel 1.4 GHz Itanium 2 ?
1.8?
6.4?
?
Pthreads?概述?
使用線程設(shè)計程序?
并行編程:??
在現(xiàn)代多CPU機器上,pthread非常適于并行編程。可以用于并行程序設(shè)計的,也可以用于pthread程序設(shè)計。?并行程序要考慮許多,如下:? 用什么并行程序設(shè)計模型??問題劃分?加載平衡(Load balancing)?通信?數(shù)據(jù)依賴?同步和競爭條件?內(nèi)存問題?I/O問題?程序復(fù)雜度?程序員的努力/花費/時間?... ?包含這些主題超出本教程的范圍,有興趣的讀者可以快速瀏覽下“Introduction to Parallel Computing”教程。?大體上,為了使用Pthreads的優(yōu)點,必須將任務(wù)組織程離散的,獨立的,可以并發(fā)執(zhí)行的。例如,如果routine1和routine2可以互換,相互交叉和(或者)重疊,他們就可以線程化。?
?
擁有下述特性的程序可以使用pthreads:? 工作可以被多個任務(wù)同時執(zhí)行,或者數(shù)據(jù)可以同時被多個任務(wù)操作。?阻塞與潛在的長時間I/O等待。?在某些地方使用很多CPU循環(huán)而其他地方?jīng)]有。?對異步事件必須響應(yīng)。?一些工作比其他的重要(優(yōu)先級中斷)。?Pthreads?也可以用于串行程序,模擬并行執(zhí)行。很好例子就是經(jīng)典的web瀏覽器,對于多數(shù)人,運行于單CPU的桌面/膝上機器,許多東西可以同時“顯示”出來。?使用線程編程的幾種常見模型:?管理者/工作者(Manager/worker):一個單線程,作為管理器將工作分配給其它線程(工作者),典型的,管理器處理所有輸入和分配工作給其它任務(wù)。至少兩種形式的manager/worker模型比較常用:靜態(tài)worker池和動態(tài)worker池。?管道(Pipeline):任務(wù)可以被劃分為一系列子操作,每一個被串行處理,但是不同的線程并發(fā)處理。汽車裝配線可以很好的描述這個模型。?Peer:?和manager/worker模型相似,但是主線程在創(chuàng)建了其它線程后,自己也參與工作。?
共享內(nèi)存模型(Shared Memory Model):??
所有線程可以訪問全局,共享內(nèi)存?線程也有自己私有的數(shù)據(jù)?程序員負責(zé)對全局共享數(shù)據(jù)的同步存?。ūWo)?
?
線程安全(Thread-safeness):??
線程安全:簡短的說,指程序可以同時執(zhí)行多個線程卻不會“破壞“共享數(shù)據(jù)或者產(chǎn)生“競爭”條件的能力。?例如:假設(shè)你的程序創(chuàng)建了幾個線程,每一個調(diào)用相同的庫函數(shù):? 這個庫函數(shù)存取/修改了一個全局結(jié)構(gòu)或內(nèi)存中的位置。?當(dāng)每個線程調(diào)用這個函數(shù)時,可能同時去修改這個全局結(jié)構(gòu)活內(nèi)存位置。?如果函數(shù)沒有使用同步機制去阻止數(shù)據(jù)破壞,這時,就不是線程安全的了。?
?
如果你不是100%確定外部庫函數(shù)是線程安全的,自己負責(zé)所可能引發(fā)的問題。?建議:小心使用庫或者對象,當(dāng)不能明確確定是否是線程安全的。若有疑慮,假設(shè)其不是線程安全的直到得以證明。可以通過不斷地使用不確定的函數(shù)找出問題所在。?
?
Pthreads API?
?
Pthreads API在ANSI/IEEE POSIX 1003.1 – 1995標(biāo)準(zhǔn)中定義。不像MPI,該標(biāo)準(zhǔn)不是免費的,必須向IEEE購買。?Pthreads API中的函數(shù)可以非正式的劃分為三大類:?線程管理(Thread management):?第一類函數(shù)直接用于線程:創(chuàng)建(creating),分離(detaching),連接(joining)等等。包含了用于設(shè)置和查詢線程屬性(可連接,調(diào)度屬性等)的函數(shù)。?互斥量(Mutexes):?第二類函數(shù)是用于線程同步的,稱為互斥量(mutexes),是"mutual exclusion"的縮寫。Mutex函數(shù)提供了創(chuàng)建,銷毀,鎖定和解鎖互斥量的功能。同時還包括了一些用于設(shè)定或修改互斥量屬性的函數(shù)。?條件變量(Condition variables):第三類函數(shù)處理共享一個互斥量的線程間的通信,基于程序員指定的條件。這類函數(shù)包括指定的條件變量的創(chuàng)建,銷毀,等待和受信(signal)。設(shè)置查詢條件變量屬性的函數(shù)也包含其中。?命名約定:線程庫中的所有標(biāo)識符都以pthread開頭?
Routine Prefix?
Functional Group?
pthread_?
線程本身和各種相關(guān)函數(shù)?
pthread_attr_?
線程屬性對象?
pthread_mutex_?
互斥量?
pthread_mutexattr_?
互斥量屬性對象?
pthread_cond_?
條件變量?
pthread_condattr_?
條件變量屬性對象?
pthread_key_?
線程數(shù)據(jù)鍵(Thread-specific data keys)?
在API的設(shè)計中充滿了不透明對象的概念,基本調(diào)用可以創(chuàng)建或修改不透明對象。不透明的對象可以被一些屬性函數(shù)調(diào)用修改。?Pthread API包含了60多個函數(shù)。該教程僅限于一部分(對于剛開始學(xué)習(xí)Pthread的程序是非常有用的)。?為了可移植性,使用Pthread庫時,pthread.h頭文件必須在每個源文件中包含。?現(xiàn)行POSIX標(biāo)準(zhǔn)僅定義了C語言的使用。Fortran程序員可以嵌入C函數(shù)調(diào)用使用,有些Fortran編譯器(像IBM AIX Fortran)可能提供了Fortran pthreads API。?關(guān)于Pthreads有些比較優(yōu)秀的書籍。其中一些在該教程的參考一節(jié)列出。?
編譯多線程程序?
?
下表列出了一些編譯使用了pthreads庫程序的命令:?
Compiler / Platform?
Compiler Command?
Description?
IBM?
AIX?
xlc_r ?/? cc_r?
C (ANSI ?/? non-ANSI)?
xlC_r?
C++?
xlf_r -qnosave
xlf90_r -qnosave?
Fortran - using IBM's Pthreads API (non-portable)?
INTEL
Linux?
icc -pthread?
C?
icpc -pthread?
C++?
PathScale
Linux?
pathcc -pthread?
C?
pathCC -pthread?
C++?
PGI
Linux?
pgcc -lpthread?
C?
pgCC -lpthread?
C++?
GNU
Linux, AIX?
gcc -pthread?
GNU C?
g++ -pthread?
GNU C++?
?
線程管理(Thread Management)?
創(chuàng)建和結(jié)束線程?
函數(shù):??
pthread_create?(thread,attr,start_routine,arg) ?
pthread_exit?(status)??
pthread_attr_init?(attr) ?
pthread_attr_destroy?(attr)??
創(chuàng)建線程:??
最初,main函數(shù)包含了一個缺省的線程。其它線程則需要程序員顯式地創(chuàng)建。?pthread_create?創(chuàng)建一個新線程并使之運行起來。該函數(shù)可以在程序的任何地方調(diào)用。?pthread_create參數(shù):? thread:返回一個不透明的,唯一的新線程標(biāo)識符。?attr:不透明的線程屬性對象??梢灾付ㄒ粋€線程屬性對象,或者NULL為缺省值。?start_routine:線程將會執(zhí)行一次的C函數(shù)。?arg:?傳遞給start_routine單個參數(shù),傳遞時必須轉(zhuǎn)換成指向void的指針類型。沒有參數(shù)傳遞時,可設(shè)置為NULL。?一個進程可以創(chuàng)建的線程最大數(shù)量取決于系統(tǒng)實現(xiàn)。?一旦創(chuàng)建,線程就稱為peers,可以創(chuàng)建其它線程。線程之間沒有指定的結(jié)構(gòu)和依賴關(guān)系。?
?
?
?
Q:一個線程被創(chuàng)建后,怎么知道操作系統(tǒng)何時調(diào)度該線程使之運行??
A:除非使用了Pthreads的調(diào)度機制,否則線程何時何地被執(zhí)行取決于操作系統(tǒng)的實現(xiàn)。強壯的程序應(yīng)該不依賴于線程執(zhí)行的順序。
?
線程屬性:??
線程被創(chuàng)建時會帶有默認的屬性。其中的一些屬性可以被程序員用線程屬性對象來修改。?pthread_attr_init?和?pthread_attr_destroy用于初始化/銷毀先成屬性對象。?其它的一些函數(shù)用于查詢和設(shè)置線程屬性對象的指定屬性。?一些屬性下面將會討論。?
結(jié)束終止:??
結(jié)束線程的方法有一下幾種:? 線程從主線程(main函數(shù)的初始線程)返回。?線程調(diào)用了pthread_exit函數(shù)。?其它線程使用?pthread_cancel函數(shù)結(jié)束線程。?調(diào)用exec或者exit函數(shù),整個進程結(jié)束。?pthread_exit用于顯式退出線程。典型地,pthread_exit()函數(shù)在線程完成工作時,不在需要時候被調(diào)用,退出線程。?如果main()在其他線程創(chuàng)建前用pthread_exit()退出了,其他線程將會繼續(xù)執(zhí)行。否則,他們會隨著main的結(jié)束而終止。?程序員可以可選擇的指定終止?fàn)顟B(tài),當(dāng)任何線程連接(join)該線程時,該狀態(tài)就返回給連接(join)該線程的線程。?清理:pthread_exit()函數(shù)并不會關(guān)閉文件,任何在線程中打開的文件將會一直處于打開狀態(tài),知道線程結(jié)束。?討論:對于正常退出,可以免于調(diào)用pthread_exit()。當(dāng)然,除非你想返回一個返回值。然而,在main中,有一個問題,就是當(dāng)main結(jié)束時,其它線程還沒有被創(chuàng)建。如果此時沒有顯式的調(diào)用pthread_exit(),當(dāng)main結(jié)束時,進程(和所有線程)都會終止。可以在main中調(diào)用pthread_exit(),此時盡管在main中已經(jīng)沒有可執(zhí)行的代碼了,進程和所有線程將保持存活狀態(tài),。?
例子: Pthread?創(chuàng)建和終止?
該例用pthread_create()創(chuàng)建了5個線程。每一個線程都會打印一條“Hello World”的消息,然后調(diào)用pthread_exit()終止線程。?
Example Code - Pthread Creation and Termination??
#include
#include
#define NUM_THREADS?????5?
?
void *PrintHello(void *threadid)?
{?
???int tid;?
???tid = (int)threadid;?
???printf("Hello World! It's me, thread #%d!/n", tid);?
???pthread_exit(NULL);?
}?
?
int main (int argc, char *argv[])?
{?
???pthread_t threads[NUM_THREADS];?
???int rc, t;?
???for(t=0; t<NUM_THREADS; t++){?
??????printf("In main: creating thread %d/n", t);?
??????rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);?
??????if (rc){?
?????????printf("ERROR; return code from pthread_create() is %d/n", rc);?
?????????exit(-1);?
??????}?
???}?
???pthread_exit(NULL);?
}?
?
?
線程管理?
向線程傳遞參數(shù)?
pthread_create()函數(shù)允許程序員想線程的start routine傳遞一個參數(shù)。當(dāng)多個參數(shù)需要被傳遞時,可以通過定義一個結(jié)構(gòu)體包含所有要傳的參數(shù),然后用pthread_create()傳遞一個指向改結(jié)構(gòu)體的指針,來打破傳遞參數(shù)的個數(shù)的限制。?所有參數(shù)都應(yīng)該傳引用傳遞并轉(zhuǎn)化成(void*)。?
?
?
Q:怎樣安全地向一個新創(chuàng)建的線程傳遞數(shù)據(jù)??
A:確保所傳遞的數(shù)據(jù)是線程安全的(不能被其他線程修改)。下面三個例子演示了那個應(yīng)該和那個不應(yīng)該。?
?
Example 1 - Thread Argument Passing??
下面的代碼片段演示了如何向一個線程傳遞一個簡單的整數(shù)。主線程為每一個線程使用一個唯一的數(shù)據(jù)結(jié)構(gòu),確保每個線程傳遞的參數(shù)是完整的。?
int *taskids[NUM_THREADS];?
?
for(t=0; t<NUM_THREADS; t++)?
{?
???taskids[t] = (int *) malloc(sizeof(int));?
???*taskids[t] = t;?
???printf("Creating thread %d/n", t);?
???rc = pthread_create(&threads[t], NULL, PrintHello, ?
????????(void *) taskids[t]);?
???...?
}?
?
?
Example 2 - Thread Argument Passing??
例子展示了用結(jié)構(gòu)體向線程設(shè)置/傳遞參數(shù)。每個線程獲得一個唯一的結(jié)構(gòu)體實例。?
struct thread_data{?
???int??thread_id;?
???int??sum;?
???char *message;?
};?
?
struct thread_data thread_data_array[NUM_THREADS];?
?
void *PrintHello(void *threadarg)?
{?
???struct thread_data *my_data;?
???...?
???my_data = (struct thread_data *) threadarg;?
???taskid = my_data->thread_id;?
???sum = my_data->sum;?
???hello_msg = my_data->message;?
???...?
}?
?
int main (int argc, char *argv[])?
{?
???...?
???thread_data_array[t].thread_id = t;?
???thread_data_array[t].sum = sum;?
???thread_data_array[t].message = messages[t];?
???rc = pthread_create(&threads[t], NULL, PrintHello, ?
????????(void *) &thread_data_array[t]);?
???...?
}?
?
?
Example 3 - Thread Argument Passing?(Incorrect)??
例子演示了錯誤地傳遞參數(shù)。循環(huán)會在線程訪問傳遞的參數(shù)前改變傳遞給線程的地址的內(nèi)容。?
int rc, t;?
?
for(t=0; t<NUM_THREADS; t++) ?
{?
???printf("Creating thread %d/n", t);?
???rc = pthread_create(&threads[t], NULL, PrintHello, ?
????????(void *) &t);?
???...?
}?
?
?
線程管理?
連接(Joining)和分離(Detaching)線程?
函數(shù):??
pthread_join?(threadid,status) ?
pthread_detach?(threadid,status)??
pthread_attr_setdetachstate?(attr,detachstate) ?
pthread_attr_getdetachstate?(attr,detachstate)??
連接:??
?“連接”是一種在線程間完成同步的方法。例如:?
?
pthread_join()函數(shù)阻賽調(diào)用線程知道threadid所指定的線程終止。?如果在目標(biāo)線程中調(diào)用pthread_exit(),程序員可以在主線程中獲得目標(biāo)線程的終止?fàn)顟B(tài)。?連接線程只能用pthread_join()連接一次。若多次調(diào)用就會發(fā)生邏輯錯誤。?兩種同步方法,互斥量(mutexes)和條件變量(condition variables),稍后討論。?
可連接(Joinable or Not)???
當(dāng)一個線程被創(chuàng)建,它有一個屬性定義了它是可連接的(joinable)還是分離的(detached)。只有是可連接的線程才能被連接(joined),若果創(chuàng)建的線程是分離的,則不能連接。?POSIX標(biāo)準(zhǔn)的最終草案指定了線程必須創(chuàng)建成可連接的。然而,并非所有實現(xiàn)都遵循此約定。?使用pthread_create()的attr參數(shù)可以顯式的創(chuàng)建可連接或分離的線程,典型四步如下:? 聲明一個pthread_attr_t數(shù)據(jù)類型的線程屬性變量?用?pthread_attr_init()初始化改屬性變量?用pthread_attr_setdetachstate()設(shè)置可分離狀態(tài)屬性?完了后,用pthread_attr_destroy()釋放屬性所占用的庫資源?
分離(Detaching):??
pthread_detach()可以顯式用于分離線程,盡管創(chuàng)建時是可連接的。?沒有與pthread_detach()功能相反的函數(shù)?
建議:??
若線程需要連接,考慮創(chuàng)建時顯式設(shè)置為可連接的。因為并非所有創(chuàng)建線程的實現(xiàn)都是將線程創(chuàng)建為可連接的。?若事先知道線程從不需要連接,考慮創(chuàng)建線程時將其設(shè)置為可分離狀態(tài)。一些系統(tǒng)資源可能需要釋放。?
例子: Pthread Joining?
Example Code - Pthread Joining??
這個例子演示了用Pthread join函數(shù)去等待線程終止。因為有些實現(xiàn)并不是默認創(chuàng)建線程是可連接狀態(tài),例子中顯式地將其創(chuàng)建為可連接的。??
#include
#include
#define NUM_THREADS????3?
?
void *BusyWork(void *null)?
{?
???int i;?
???double result=0.0;?
???for (i=0; i<1000000; i++)?
???{?
?????result = result + (double)random();?
???}?
???printf("result = %e/n",result);?
???pthread_exit((void *) 0);?
}?
?
int main (int argc, char *argv[])?
{?
???pthread_t thread[NUM_THREADS];?
???pthread_attr_t attr;?
???int rc, t;?
? ?void *status;?
?
???/* Initialize and set thread detached attribute */?
???pthread_attr_init(&attr);?
???pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);?
?
???for(t=0; t<NUM_THREADS; t++)?
???{?
??????printf("Creating thread %d/n", t);?
??????rc = pthread_create(&thread[t], &attr, BusyWork, NULL);??
??????if (rc)?
??????{?
?????????printf("ERROR; return code from pthread_create() ?
????????????????is %d/n", rc);?
?????????exit(-1);?
??????}?
???}?
?
???/* Free attribute and wait for the other threads */?
???pthread_attr_destroy(&attr);?
???for(t=0; t<NUM_THREADS; t++)?
???{?
??????rc = pthread_join(thread[t], &status);?
??????if (rc)?
??????{?
?????????printf("ERROR; return code from pthread_join() ?
????????????????is %d/n", rc);?
?????????exit(-1);?
??????}?
??????printf("Completed join with thread %d status= %ld/n",t, (long)status);?
???}?
?
???pthread_exit(NULL);?
}?
?
?
線程管理?
棧管理?
函數(shù):??
pthread_attr_getstacksize?(attr, stacksize) ?
pthread_attr_setstacksize?(attr, stacksize)??
pthread_attr_getstackaddr?(attr, stackaddr) ?
pthread_attr_setstackaddr?(attr, stackaddr)??
防止棧問題:??
POSIX標(biāo)準(zhǔn)并沒有指定線程棧的大小,依賴于實現(xiàn)并隨實現(xiàn)變化。?很容易超出默認的棧大小,常見結(jié)果:程序終止或者數(shù)據(jù)損壞。?安全和可移植的程序應(yīng)該不依賴于默認的棧限制,但是取而代之的是用pthread_attr_setstacksize分配足夠的棧大小。?pthread_attr_getstackaddr和pthread_attr_setstackaddr函數(shù)可以被程序用于將棧設(shè)置在指定的內(nèi)存區(qū)域。?
在LC上的一些實際例子:??
默認棧大小經(jīng)常變化很大,最大值也變化很大,可能會依賴于每個節(jié)點的線程數(shù)目。?
Node
Architecture?
#CPUs?
Memory (GB)?
Default Size
(bytes)?
AMD Opteron?
8?
16?
2,097,152?
Intel IA64?
4?
8?
33,554,432?
Intel IA32?
2?
4?
2,097,152?
IBM Power5?
8?
32?
196,608?
IBM Power4?
8?
16?
196,608?
IBM Power3?
16?
16?
98,304?
例子:?棧管理?
Example Code - Stack Management??
這個例子演示了如何去查詢和設(shè)定線程棧大小。??
#include
#include
#define NTHREADS 4?
#define N 1000?
#define MEGEXTRA 1000000?
??
pthread_attr_t attr;?
??
void *dowork(void *threadid)?
{?
???double A[N][N];?
???int i,j,tid;?
???size_t mystacksize;?
?
???tid = (int)threadid;?
???pthread_attr_getstacksize (&attr, &mystacksize);?
???printf("Thread %d: stack size = %li bytes /n", tid, mystacksize);?
???for (i=0; i<N; i++)?
?????for (j=0; j<N; j++)?
??????A[i][j] = ((i*j)/3.452) + (N-i);?
???pthread_exit(NULL);?
}?
??
int main(int argc, char *argv[])?
{?
???pthread_t threads[NTHREADS];?
???size_t stacksize;?
???int rc, t;?
??
???pthread_attr_init(&attr);?
???pthread_attr_getstacksize (&attr, &stacksize);?
???printf("Default stack size = %li/n", stacksize);?
???stacksize = sizeof(double)*N*N+MEGEXTRA;?
???printf("Amount of stack needed per thread = %li/n",stacksize);?
???pthread_attr_setstacksize (&attr, stacksize);?
???printf("Creating threads with stack size = %li bytes/n",stacksize);?
???for(t=0; t<NTHREADS; t++){?
??????rc =?pthread_create(&threads[t], &attr, dowork, (void *)t);?
??????if (rc){?
?????????printf("ERROR; return code from pthread_create() is %d/n", rc);?
?????????exit(-1);?
??????}?
???}?
???printf("Created %d threads./n", t);?
???pthread_exit(NULL);?
}?
?
線程管理?
其他各種函數(shù):?
pthread_self?() ?
pthread_equal?(thread1,thread2)??
pthread_self返回調(diào)用該函數(shù)的線程的唯一,系統(tǒng)分配的線程ID。?pthread_equal比較兩個線程ID,若不同返回0,否則返回非0值。?注意這兩個函數(shù)中的線程ID對象是不透明的,不是輕易能檢查的。因為線程ID是不透明的對象,所以C語言的==操作符不能用于比較兩個線程ID。?
pthread_once?(once_control, init_routine)??
pthread_once?在一個進程中僅執(zhí)行一次init_routine。任何線程第一次調(diào)用該函數(shù)會執(zhí)行給定的init_routine,不帶參數(shù),任何后續(xù)調(diào)用都沒有效果。?init_routine函數(shù)一般是初始化的程序?once_control參數(shù)是一個同步結(jié)構(gòu)體,需要在調(diào)用pthread_once前初始化。例如:?
pthread_once_t once_control = PTHREAD_ONCE_INIT;??
?
?
互斥量(Mutex Variables)?
概述?
互斥量(Mutex)是“mutual exclusion”的縮寫?;コ饬渴菍崿F(xiàn)線程同步,和保護同時寫共享數(shù)據(jù)的主要方法?互斥量對共享數(shù)據(jù)的保護就像一把鎖。在Pthreads中,任何時候僅有一個線程可以鎖定互斥量,因此,當(dāng)多個線程嘗試去鎖定該互斥量時僅有一個會成功。直到鎖定互斥量的線程解鎖互斥量后,其他線程才可以去鎖定互斥量。線程必須輪著訪問受保護數(shù)據(jù)。?互斥量可以防止“競爭”條件。下面的例子是一個銀行事務(wù)處理時發(fā)生了競爭條件:?
Thread 1?
Thread 2?
Balance?
Read balance: $1000?
??
$1000?
??
Read balance: $1000?
$1000?
??
Deposit $200?
$1000?
Deposit $200?
??
$1000?
Update balance $1000+$200?
??
$1200?
??
Update balance $1000+$200?
$1200?
?
上面的例子,當(dāng)一個線程使用共享數(shù)據(jù)資源時,應(yīng)該用一個互斥量去鎖定“Balance”。?一個擁有互斥量的線程經(jīng)常用于更新全局變量。確保了多個線程更新同樣的變量以安全的方式運行,最終的結(jié)果和一個線程處理的結(jié)果是相同的。這個更新的變量屬于一個“臨界區(qū)(critical section)”。?使用互斥量的典型順序如下:? 創(chuàng)建和初始一個互斥量?多個線程嘗試去鎖定該互斥量?僅有一個線程可以成功鎖定改互斥量?鎖定成功的線程做一些處理?線程解鎖該互斥量?另外一個線程獲得互斥量,重復(fù)上述過程?最后銷毀互斥量?當(dāng)多個線程競爭同一個互斥量時,失敗的線程會阻塞在lock調(diào)用處。可以用“trylock”替換“l(fā)ock”,則失敗時不會阻塞。?當(dāng)保護共享數(shù)據(jù)時,程序員有責(zé)任去確認是否需要使用互斥量。如,若四個線程會更新同樣的數(shù)據(jù),但僅有一個線程用了互斥量,則數(shù)據(jù)可能會損壞。?
?
互斥量(Mutex Variables)?
創(chuàng)建和銷毀互斥量?
函數(shù):??
pthread_mutex_init?(mutex,attr) ?
pthread_mutex_destroy?(mutex)??
pthread_mutexattr_init?(attr) ?
pthread_mutexattr_destroy?(attr)??
用法:??
互斥量必須用類型pthread_mutex_t類型聲明,在使用前必須初始化,這里有兩種方法可以初始化互斥量:?
聲明時靜態(tài)地,如:
pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;??動態(tài)地用pthread_mutex_init()函數(shù),這種方法允許設(shè)定互斥量的屬性對象attr。?
互斥量初始化后是解鎖的。?
attr對象用于設(shè)置互斥量對象的屬性,使用時必須聲明為pthread_mutextattr_t類型,默認值可以是NULL。Pthreads標(biāo)準(zhǔn)定義了三種可選的互斥量屬性:?? 協(xié)議(Protocol):?指定了協(xié)議用于阻止互斥量的優(yōu)先級改變?優(yōu)先級上限(Prioceiling):指定互斥量的優(yōu)先級上限?進程共享(Process-shared):指定進程共享互斥量?
注意所有實現(xiàn)都提供了這三個可先的互斥量屬性。?
pthread_mutexattr_init()和pthread_mutexattr_destroy()函數(shù)分別用于創(chuàng)建和銷毀互斥量屬性對象。?pthread_mutex_destroy()應(yīng)該用于釋放不需要再使用的互斥量對象。?
?
互斥量(Mutex Variables)?
鎖定和解鎖互斥量?
函數(shù):??
pthread_mutex_lock?(mutex) ?
pthread_mutex_trylock?(mutex)??
pthread_mutex_unlock?(mutex)??
用法:??
線程用pthread_mutex_lock()函數(shù)去鎖定指定的mutex變量,若該mutex已經(jīng)被另外一個線程鎖定了,該調(diào)用將會阻塞線程直到mutex被解鎖。?pthread_mutex_trylock()?will attempt to lock a mutex. However, if the mutex is already locked, the routine will return immediately with a "busy" error code. This routine may be useful in ?pthread_mutex_trylock()嘗試著去鎖定一個互斥量,然而,若互斥量已被鎖定,程序會立刻返回并返回一個忙錯誤值。該函數(shù)在優(yōu)先級改變情況下阻止死鎖是非常有用的。?線程可以用pthread_mutex_unlock()解鎖自己占用的互斥量。在一個線程完成對保護數(shù)據(jù)的使用,而其它線程要獲得互斥量在保護數(shù)據(jù)上工作時,可以調(diào)用該函數(shù)。若有一下情形則會發(fā)生錯誤:? 互斥量已經(jīng)被解鎖?互斥量被另一個線程占用?互斥量并沒有多么“神奇”的,實際上,它們就是參與的線程的“君子約定”。寫代碼時要確信正確地鎖定,解鎖互斥量。下面演示了一種邏輯錯誤:?
·????????????????????Thread 1???? Thread 2???? Thread 3?
·????????????????????Lock?????????Lock??????????
·????????????????????A = 2????????A = A+1??????A = A*B?
·????????????????????Unlock???????Unlock?????
?
?
Q:有多個線程等待同一個鎖定的互斥量,當(dāng)互斥量被解鎖后,那個線程會第一個鎖定互斥量??
A:除非線程使用了優(yōu)先級調(diào)度機制,否則,線程會被系統(tǒng)調(diào)度器去分配,那個線程會第一個鎖定互斥量是隨機的。?
例子:使用互斥量?
Example Code - Using Mutexes??
例程演示了線程使用互斥量處理一個點積(dot product)計算。主數(shù)據(jù)通過一個可全局訪問的數(shù)據(jù)結(jié)構(gòu)被所有線程使用,每個線程處理數(shù)據(jù)的不同部分,主線程等待其他線程完成計算并輸出結(jié)果。?
#include
#include
#include
?
/*????
The following structure contains the necessary information???
to allow the function "dotprod" to access its input data and ?
place its output into the structure.???
*/?
?
typedef struct ?
?{?
???double??????*a;?
???double??????*b;?
???double?????sum; ?
???int?????veclen; ?
?} DOTDATA;?
?
/* Define globally accessible variables and a mutex */?
?
#define NUMTHRDS 4?
#define VECLEN 100?
???DOTDATA dotstr; ?
???pthread_t callThd[NUMTHRDS];?
???pthread_mutex_t mutexsum;?
?
/*?
The function dotprod is activated when the thread is created.?
All input to this routine is obtained from a structure ?
of type DOTDATA and all output from this function is written into?
this structure. The benefit of this approach is apparent for the ?
multi-threaded program: when a thread is created we pass a single?
argument to the activated function - typically this argument?
is a thread number. All??the other information required by the ?
function is accessed from the globally accessible structure. ?
*/?
?
void *dotprod(void *arg)?
{?
?
???/* Define and use local variables for convenience */?
?
???int i, start, end, offset, len ;?
???double mysum, *x, *y;?
???offset = (int)arg;?
??????
???len = dotstr.veclen;?
???start = offset*len;?
???end???= start + len;?
???x = dotstr.a;?
???y = dotstr.b;?
?
???/*?
???Perform the dot product and assign result?
???to the appropriate variable in the structure. ?
???*/?
?
???mysum = 0;?
???for (i=start; i<end ; i++) ?
????{?
??????mysum += (x[i] * y[i]);?
????}?
?
???/*?
???Lock a mutex prior to updating the value in the shared?
???structure, and unlock it upon updating.?
???*/?
???pthread_mutex_lock (&mutexsum);?
???dotstr.sum += mysum;?
???pthread_mutex_unlock (&mutexsum);?
?
???pthread_exit((void*) 0);?
}?
?
/* ?
The main program creates threads which do all the work and then ?
print out result upon completion. Before creating the threads,?
the input data is created. Since all threads update a shared structure, ?
we need a mutex for mutual exclusion. The main thread needs to wait for?
all threads to complete, it waits for each one of the threads. We specify?
a thread attribute value that allow the main thread to join with the?
threads it creates. Note also that we free up handles when they are?
no longer needed.?
*/?
?
int main (int argc, char *argv[])?
{?
???int i;?
???double *a, *b;?
???void *status;?
???pthread_attr_t attr;?
?
???/* Assign storage and initialize values */?
???a = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));?
???b = (double*) malloc (NUMTHRDS*VECLEN*sizeof(double));?
???
???for (i=0; i<VECLEN*NUMTHRDS; i++