




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
BlaiseBarney,LawrenceLivermoreNationalLaboratory目錄表摘要Pthreads概述什么是線程?什么是Pthreads?為什么使用Pthreads?使用線程設計程序"PthreadsAPI編譯多線程程序線程管理創建和終止線程向線程傳遞參數連接(Joining)和分離(Detaching)線程棧管理其它函數互斥量(MutexVariables)互斥量概述創建和銷毀互斥量鎖定(Locking)和解鎖(Unlocking)互斥量條件變量(ConditionVariable)條件變量概述創建和銷毀條件變量等待(Waiting)和發送信號(Signaling)沒有覆蓋的主題Pthread庫API參考參考資料在多處理器共享內存的架構中(如:對稱多處理系統SMP),線程可以用于實現程序的并行性。歷史上硬件銷售商實現了各種私有版本的多線程庫,使得軟件開發者不得不關心它的移植性。對于UNIX系統,IEEEPOSIX1003.1標準定義了一個C語言多線程編程接口。依附于該標準的實現被稱為POSIXtheads或Pthreads。該教程介紹了Pthreads的概念、動機和設計思想。內容包含了PthreadsAPI主要的三大類函數:線程管理(ThreadManagment)、互斥量(MutexVariables)和條件變量(ConditionVariables)。向剛開始學習Pthreads的程序員提供了演示例程。適于:剛開始學習使用線程實現并行程序設計;對于C并行程序設計有基本了解。不熟悉并行程序設計的可以參考EC3500:IntroductionToParallelComputingoPthreads概述什么是線程?技術上,線程可以定義為:可以被操作系統調度的獨立的指令流。但是這是什么意思呢?對于軟件開發者,在主程序中運行的“函數過程”可以很好的描述線程的概念。?進一步,想象下主程序(a.out)包含了許多函數,操作系統可以調度這些函數,使之同時或者(和)獨立的執行。這就描述了“多線程”程序。怎樣完成的呢??在理解線程之前,應先對UNIX進程(process)有所了解。進程被操作系統創建,需要相當多的“額外開銷”。進程包含了程序的資源和執行狀態信息。如下:o進程ID,進程groupID,用戶ID和groupIDo環境o工作目錄o程序指令o寄存器o棧o堆o文件描述符o信號動作(Signalactions)o共享庫o進程間通信工具(如:消息隊列,管道,信號量或共享內存)
UserAddressSpaceUserAddressSpaceThread2dataheaproutlnelvad()g蟲()Thread1Me/tfou11ne1柿varlwa.r2marnG匸口口tm*el()UserAddressSpaceUserAddressSpaceThread2dataheaproutlnelvad()g蟲()Thread1Me/tfou11ne1柿varlwa.r2marnG匸口口tm*el()rant (}ProcessIIUserIDGroupIDStackPairPrgrm.CaRegEstersroutinQ2()va.rl\rar2varSStackPollPr^riri.CoRegistersmain()EQUt-l口亡丄cQutine2dataFilesLocksSocketsUNIXPROCESSTHREADSWITHINAUNIX
PROCESSUNIXPROCESS?線程使用并存在于進程資源中,還可以被操作系統調用并獨立地運行,這主要是因為線程僅僅復制必要的資源以使自己得以存在并執行。?獨立的控制流得以實現是因為線程維持著自己的:o堆棧指針o寄存器o調度屬性(如:策略或優先級)o待定的和阻塞的信號集合(Setofpendingandblockedsignals)o線程專用數據(TSD:ThreadSpecificData.)?因此,在UNIX環境下線程:o存在于進程,使用進程資源o擁有自己獨立的控制流,只要父進程存在并且操作系統支持o只復制必可以使得獨立調度的必要資源o可以和其他線程獨立(或非獨立的)地共享進程資源o當父進程結束時結束,或者相關類似的o是“輕型的”,因為大部分額外開銷已經在進程創建時完成了?因為在同一個進程中的線程共享資源:o一個線程對系統資源(如關閉一個文件)的改變對所有其它線程是可以見的o兩個同樣值的指針指向相同的數據o讀寫同一個內存位置是可能的,因此需要成員顯式地使用同步Pthreads概述什么是Pthreads??歷史上,硬件銷售商實現了私有版本的多線程庫。這些實現在本質上各自不同,使得程序員難于開發可移植的應用程序。?為了使用線程所提供的強大優點,需要一個標準的程序接口。對于UNIX系統,IEEEPOSIX1003.1c(1995)標準制訂了這一標準接口。依賴于該標準的實現就稱為POSIXthreads或者Pthreads。現在多數硬件銷售商也提供Pthreads,附加于私有的API。?Pthreads被定義為一些C語言類型和函數調用,用pthread.h頭(包含)文件和線程庫實現。這個庫可以是其它庫的一部分,如libc。Pthreads概述為什么使用Pthreads?使用Pthreads的主要動機是提高潛在程序的性能。當與創建和管理進程的花費相比,線程可以使用操作系統較少的開銷,管理線程需要較少的系統資源。例如,下表比較了fork()函數和pthread_create()函數所用的時間。計時反應了50,000個進程/線程的創建,使用時間工具實現,單位是秒,沒有優化標志。備注:不要期待系統和用戶時間加起來就是真實時間,因為這些SMP系統有多個CPU同時工作。這些都是近似值。平臺fork()pthread_create()realusersysrealusersysAMD2.4GHzOpteron(8cpus/node)41.0760.089.010.660.190.43IBM1.9GHzPOWER5p5-575(8cpus/node)64.2430.7827.681.750.691.10IBM1.5GHzPOWER4104.0548.6447.212.011.001.52(8cpus/node)INTEL2.4GHzXeon(2cpus/node)54.951.5420.781.640.670.90INTEL1.4GHzItanium2(4cpus/node)54.541.0722.222.031.260.67forkvsthread.txt?在同一個進程中的所有線程共享同樣的地址空間。較于進程間的通信,在許多情況下線程間的通信效率比較高,且易于使用。?較于沒有使用線程的程序,使用線程的應用程序有潛在的性能增益和實際的優點:oCPU使用I/O交疊工作:例如,一個程序可能有一個需要較長時間的I/O操作,當一個線程等待I/O系統調用完成時,CPU可以被其它線程使用。o優先/實時調度:比較重要的任務可以被調度,替換或者中斷較低優先級的任務。o異步事件處理:頻率和持續時間不確定的任務可以交錯。例如,web服務器可以同時為前一個請求傳輸數據和管理新請求。?考慮在SMP架構上使用Pthreads的主要動機是獲的最優的性能。特別的,如果一個程序使用MPI在節點通信,使用Pthreads可以使得節點數據傳輸得到顯著提高。?例如:oMPI庫經常用共享內存實現節點任務通信,這至少需要一次內存復制操作(進程到進程)。oPthreads沒有中間的內存復制,因為線程和一個進程共享同樣的地址空間。沒有數據傳輸。變成cache-to-CPU或memory-to-CPU的帶寬(最壞情況),速度是相當的快。o比較如下:PlatformMPISharedMemoryBandwidth(GB/sec)PthreadsWorstCaseMemory-to-CPUBandwidth(GB/sec)AMD2.4GHzOpteron1.25.3IBM1.9GHzPOWER5p5-5754.116IBM1.5GHzP0WER42.14Intel1.4GHzXeon0.34.3Intel1.4GHzItanium21.86.4Pthreads概述使用線程設計程序丫并行編程:?在現代多CPU機器上,pthread非常適于并行編程。可以用于并行程序設計的,也可以用于pthread程序設計。?并行程序要考慮許多,如下:o用什么并行程序設計模型?o問題劃分o加載平衡(Loadbalancing)o通信o數據依賴o同步和競爭條件o內存問題oI/O問題o程序復雜度o程序員的努力/花費/時間o…?包含這些主題超出本教程的范圍,有興趣的讀者可以快速瀏覽下“IntroductiontoParallelComputing"教程。?大體上,為了使用Pthreads的優點,必須將任務組織程離散的,獨立的,可以并發執行的。例如,如果routinel和routine2可以互換,相互交叉和或者)重疊,他們就可以線程化。routinelroutine?finalroutineroutinelroutine?finalroutineUnalroutineroutinelUnalroutineroutinelroutines?擁有下述特性的程序可以使用pthreads:o工作可以被多個任務同時執行,或者數據可以同時被多個任務操作。o阻塞與潛在的長時間I/O等待。o在某些地方使用很多CPU循環而其他地方沒有。o對異步事件必須響應。o一些工作比其他的重要(優先級中斷)。?Pthreads也可以用于串行程序,模擬并行執行。很好例子就是經典的web瀏覽器,對于多數人,運行于單CPU的桌面/膝上機器,許多東西可以同時“顯示”出來。?使用線程編程的幾種常見模型:o管理者7工作者(ManogeWo/?腕f):—個單線程,作為管理器將工作分配給其它線程(工作者),典型的,管理器處理所有輸入和分配工作給其它任務。至少兩種形式的manager/worker模型比較常用:靜態worker池和動態worker池。o管道(Pipeline):任務可以被劃分為一系列子操作,每一個被串行處理,但是不同的線程并發處理。汽車裝配線可以很好的描述這個模型。oPeer:和manager/worker模型相似,但是主線程在創建了其它線程后,自己也參與工作。?共享內存模型(SharedMemoryModel):?所有線程可以訪問全局,共享內存?線程也有自己私有的數據?程序員負責對全局共享數據的同步存取(保護)
threadthreadprivateprivatethreadthreadprivateprivateI線程安全(Thread-safeness):?線程安全:簡短的說,指程序可以同時執行多個線程卻不會“破壞“共享數據或者產生“競爭”條件的能力。?例如:假設你的程序創建了幾個線程,每一個調用相同的庫函數:
o這個庫函數存取/修改了一個全局結構或內存中的位置。o當每個線程調用這個函數時,可能同時去修改這個全局結構活內存位置。o如果函數沒有使用同步機制去阻止數據破壞,這時,就不是線程安全的了。subA?如果你不是100%確定外部庫函數是線程安全的,自己負責所可能引發的問題。?建議:小心使用庫或者對象,當不能明確確定是否是線程安全的。若有疑慮,假設其不是線程安全的直到得以證明。可以通過不斷地使用不確定的函數找出問題所在。PthreadsAPIPthreadsAPI在ANSI/IEEEPOSIX1003.1T995標準中定義。不像MPI,該標準不是免費的,必須向IEEE購買。PthreadsAPI中的函數可以非正式的劃分為三大類:線程管理血management):第一類函數直接用于線程:創建(creating),分離(detaching),連接(joining)等等。包含了用于設置和查詢線程屬性(可連接,調度屬性等)的函數。互斥量GW“te兀eQ:第二類函數是用于線程同步的,稱為互斥量(mutexes),是"mutualexclusion"的縮寫。Mutex函數提供了創建,銷毀,鎖定和解鎖互斥量的功能。同時還包括了一些用于設定或修改互斥量屬性的函數。條件變量Conditionvariables):第三類函數處理共享一個互斥量的線程間的通信,基于程序員指定的條件。這類函數包括指定的條件變量的創建,銷毀,等待和受信(signal)。設置查詢條件變量屬性的函數也包含其中。
?命名約定:線程庫中的所有標識符都以pthread開頭RoutinePrefixFunctionalGrouppthread_線程本身和各種相關函數pthread_attr_線程屬性對象pthread_mutex_互斥量pthread_mutexattr_互斥量屬性對象pthread_cond_條件變量pthread_condattr_條件變量屬性對象pthread_key_線程數據鍵(Thread-specificdatakeys)?在API的設計中充滿了不透明對象的概念,基本調用可以創建或修改不透明對象。不透明的對象可以被一些屬性函數調用修改。?PthreadAPI包含了60多個函數。該教程僅限于一部分(對于剛開始學習Pthread的程序是非常有用的)。?為了可移植性,使用Pthread庫時,pthread.h頭文件必須在每個源文件中包含。?現行POSIX標準僅定義了C語言的使用。Fortran程序員可以嵌入C函數調用使用,有些Fortran編譯器(像IBMAIXFortran)可能提供了FortranpthreadsAPI。?關于Pthreads有些比較優秀的書籍。其中一些在該教程的參考一節列出。編譯多線程程序?下表列出了一些編譯使用了pthreads庫程序的命令:■■Compiler/PlatformCompilerCommandDescriptionxlc_r/cc_rC(ANSI/non-ANSI)IBMxlC_rC++AIXxlf_r-qnosavexlf90_r-qnosaveFortran-usingIBM'sPthreadsAPI(non-portable)INTELicc-pthreadCLinuxicpc-pthreadC++PathScalepathcc-pthreadCLinuxpathCC-pthreadC++PGIpgcc-lpthreadCLinuxpgCC-lpthreadC++GNUgcc-pthreadGNUCLinux,AIXg++-pthreadGNUC++線程管理(ThreadManagement)創建和結束線程l函數:Ipthread_create(thread,attr,start_routine,arg)pthread_exit(status)pthread_attr_init(attr)pthread_attr_destroy(attr)、創建線程:?最初,main函數包含了一個缺省的線程。其它線程則需要程序員顯式地創建。pthread_create創建一個新線程并使之運行起來。該函數可以在程序的任何地方調用。pthread_create參數:thread:返回一個不透明的,唯一的新線程標識符。attr:不透明的線程屬性對象。可以指定一個線程屬性對象,或者NULL為缺省值。start_routine:線程將會執行一次的C函數。arg:傳遞給start_routii^個參數,傳遞時必須轉換成指向void的指針類型。沒有參數傳遞時,可設置為NULL。一個進程可以創建的線程最大數量取決于系統實現。一旦創建,線程就稱為peers,可以創建其它線程。線程之間沒有指定的結構和依賴關系。thread3?Q:—個線程被創建后,怎么知道操作系統何時調度該線程使之運行?A:除非使用了Pthreads的調度機制,否則線程何時何地被執行取決于操作系統的實現。強壯的程序應該不依賴于線程執行的順序。、線程屬性:?線程被創建時會帶有默認的屬性。其中的一些屬性可以被程序員用線程屬性對象來修改。pthread_attr_init和pthread_attr_destroy用于初始化/銷毀先成屬性對象。?其它的一些函數用于查詢和設置線程屬性對象的指定屬性。一些屬性下面將會討論。L結束終止:?結束線程的方法有一下幾種:O線程從主線程(main函數的初始線程)返回。o線程調用了pthread_exit函數。o其它線程使用pthread_cancel函數結束線程。o調用exec或者exit函數,整個進程結束。?pthread_exit用于顯式退出線程。典型地,pthread_exit()函數在線程完成工作時,不在需要時候被調用,退出線程。?如果main()在其他線程創建前用pthread_exit()退出了,其他線程將會繼續執行。否則,他們會隨著main的結束而終止。?程序員可以可選擇的指定終止狀態,當任何線程連接(join)該線程時,該狀態就返回給連接(join)該線程的線程。?清理:pthread_exit()函數并不會關閉文件,任何在線程中打開的文件將會一直處于打開狀態,知道線程結束。?討論:對于正常退出,可以免于調用pthread_exit()。當然,除非你想返回一個返回值。然而,在main中,有一個問題,就是當main結束時,其它線程還沒有被創建。如果此時沒有顯式的調用pthread_exit(),當main結束時,進程(和所有線程)都會終止。可以在main中調用pthread_exit(),此時盡管在main中已經沒有可執行的代碼了,進程和所有線程將保持存活狀態,。例子:Pthread創建和終止?該例用pthread_create()創建了5個線程。每一個線程都會打印一條“HelloWorld"的消息,然后調用pthread_exit()終止線程。3JExampleCode-PthreadCreationandTermination#include<pthread.h>#include<stdio.h>#defineNUM_THREADS5void*PrintHello(void*threadid){inttid;tid=(int)threadid;printf("HelloWorld!It'sme,thread#%d!\n",tid);pthread_exit(NULL);}intmain(intargc,char*argv[]){pthread_tthreads[NUM_THREADS];intrc,t;for(t=0;t<NUM_THREADS;t++){printf("Inmain:creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,(void*)t);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}pthread_exit(NULL);}卜Output線程管理向線程傳遞參數?pthread_create()函數允許程序員想線程的startroutine傳遞一個參數。當多個參數需要被傳遞時,可以通過定義一個結構體包含所有要傳的參數,然后用pthread_create()傳遞一個指向改結構體的指針,來打破傳遞參數的個數的限制。?所有參數都應該傳引用傳遞并轉化成(void*)。?Q:怎樣安全地向一個新創建的線程傳遞數據?A:確保所傳遞的數據是線程安全的(不能被其他線程修改)。下面三個例子演示了那個應該和那個不應該。Example1-ThreadArgumentPassing下面的代碼片段演示了如何向一個線程傳遞一個簡單的整數。主線程為每一個線程使用一個唯一的數據結構,確保每個線程傳遞的參數是完整的。int*taskids[NUM_THREADS];for(t=0;t<NUM_THREADS;t++){taskids[t]二(int*)malloc(sizeof(int));*taskids[t]二t;printf("Creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,(void*)taskids[t]);0utput10Example2-ThreadArgumentPassing例子展示了用結構體向線程設置/傳遞參數。每個線程獲得一個唯一的結構體實例。struetthread_data{intthread_id;intsum;char*message;};structthread_datathread_data_array[NUM_THREADS];void*PrintHello(void*threadarg){structthread_data*my_data;my_data=(structthread_data*)threadarg;taskid=my_data—>thread_id;sum=my_data—>sum;hello_msg=my_data—>message;}intmain(intargc,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]);}卜O-utputExample3-ThreadArgumentPassing (Incorrect)例子演示了錯誤地傳遞參數。循環會在線程訪問傳遞的參數前改變傳遞給線程的地址的內容。intrc,t;for(t=0;t<NUM_THREADS;t++){printf("Creatingthread%d\n",t);rc=pthread_create(&threads[t],NULL,PrintHello,
(void*)&t);卜(void*)&t);卜Output線程管理連接(Joining)和分離(Detaching)線程I函數:Ipthread_join(threadid,status)pthread_detach(threadid,status)pthread_attr_setdetachstate(attr,detachstate)pthread_attr_getdetachstate(attr,detachstate)I連接:? “連接”是一種在線程間完成同步的方法。例如:MasterThreadWorkerThreadDOWORKpthreadjoipthread,esc!MasterThreadWorkerThreadDOWORKpthreadjoipthread,esc!Worker
Thread?連接線程只能用pthread_join()連接一次。若多次調用就會發生邏輯錯誤。兩種同步方法,互斥量(mutexes)和條件變量(conditionvariables),稍后討論。?可連接(JoinableorNot)??當一個線程被創建,它有一個屬性定義了它是可連接的(joinable)還是分離的(detached)。只有是可連接的線程才能被連接(joined),若果創建的線程是分離的,則不能連接。POSIX標準的最終草案指定了線程必須創建成可連接的。然而,并非所有實現都遵循此約定。?使用pthread_create()的attr參數可以顯式的創建可連接或分離的線程,典型四步如下:聲明一個pthread_attr_t數據類型的線程屬性變量用pthread_attr_init()初始化改屬性變量用pthread_attr_setdetachstate()設置可分離狀態屬性完了后,用pthread_attr_destroy()釋放屬性所占用的庫資源I分離(Detaching):pthread_detach()可以顯式用于分離線程,盡管創建時是可連接的。?沒有與pthread_detach()功能相反的函數I建議:?若線程需要連接,考慮創建時顯式設置為可連接的。因為并非所有創建線程的實現都是將線程創建為可連接的。?若事先知道線程從不需要連接,考慮創建線程時將其設置為可分離狀態。一些系統資源可能需要釋放。例子:PthreadJoiningExampleCode-PthreadJoining這個例子演示了用Pthreadjoin函數去等待線程終止。因為有些實現并不是默認創建線程是可連接狀態,例子中顯式地將其創建為可連接的。#defineNUM_THREADS3void*BusyWork(void*null){inti;doubleresult=0.0;for(i=0;i<1000000;i++){result二result+(double)random();}printf("result二%e\n",result);pthread_exit((void*)0);}intmain(intargc,char*argv[]){pthread_tthread[NUM_THREADS];pthread_attr_tattr;intrc,t;void*status;/*Initializeandsetthreaddetachedattribute*/pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);for(t=0;t<NUM_THREADS;t++){printf("Creatingthread%d\n",t);rc=pthread_create(&thread[t],&attr,BusyWork,NULL);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}/*Freeattributeandwaitfortheotherthreads*/pthread_attr_destroy(&attr);for(t=0;t<NUM_THREADS;t++){rc=pthread_join(thread[t],&status);if(rc){printf("ERROR;returncodefrompthread_join()is%d\n",rc);exit(-l);}printf("Completedjoinwiththread%dstatus二%ld\n",t,(long)status);}pthread_exit(NULL);}—卜O-utput線程管理棧管理L函數:Ipthread_attr_getstacksize(attr,stacksize)pthread_attr_setstacksize(attr,stacksize)pthread_attr_getstackaddr(attr,stackaddr)pthread_attr_setstackaddr(attr,stackaddr)I防止棧問題:POSIX標準并沒有指定線程棧的大小,依賴于實現并隨實現變化。?很容易超出默認的棧大小,常見結果:程序終止或者數據損壞。?安全和可移植的程序應該不依賴于默認的棧限制,但是取而代之的是用pthread_attr_setstacksize分配足夠的棧大小。pthread_attr_getstackaddr和pthread_attr_setstackaddr函數可以被程序用于將棧設置在指定的內存區域。卜在lc上的一些實際例子:?默認棧大小經常變化很大,最大值也變化很大,可能會依賴于每個節點的線程數目。NodeArchitecture#CPUsMemory(GB)DefaultSize(bytes)AMDOpteron8162,097,152IntelIA644833,554,432IntelIA32242,097,152IBMPower5832196,608IBMPower4816196,608IBMPower316169&304例子:棧管理ExampleCode-StackManagement這個例子演示了如何去查詢和設定線程棧大小。#include<pthread.h>#include<stdio.h>#defineNTHREADS4#defineN1000#defineMEGEXTRA1000000pthread_attr_tattr;void*dowork(void*threadid){doubleA[N][N];inti,j,tid;size_tmystacksize;tid=(int)threadid;pthread_attr_getstacksize(&attr,&mystacksize);printf("Thread%d:stacksize=%libytes\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);}intmain(intargc,charpthread_self返回調用該函數的線程的唯一,系統分配的線程ID。pthread_self返回調用該函數的線程的唯一,系統分配的線程ID。 pthread_equal比較兩個線程ID,若不同返回0,否則返回非0值。?注意這兩個函數中的線程ID對象是不透明的,不是輕易能檢查的。因為線程ID是不透明的對象,所以C語言的==操作符不能用于比較兩個線程ID。{pthread_tthreads[NTHREADS];size_tstacksize;intrc,t;pthread_attr_init(&attr);pthread_attr_getstacksize(&attr,&stacksize);printf("Defaultstacksize=%li\n",stacksize);stacksize=sizeof(double)*N*N+MEGEXTRA;printf("Amountofstackneededperthread=%li\n",stacksize);pthread_attr_setstacksize(&attr,stacksize);printf("Creatingthreadswithstacksize=%libytes\n",stacksize);for(t=0;t〈NTHREADS;t++){rc=pthread_create(&threads[t],&attr,dowork,(void*)t);if(rc){printf("ERROR;returncodefrompthread_create()is%d\n",rc);exit(-l);}}printf("Created%dthreads.\n",t);pthread_exit(NULL);}其他各種函數:pthread_self()pthread_equal(thread1,thread2)pthread_once(once_control,init_routine)pthread_once在一個進程中僅執行一次init_routine。任何線程第一次調用該函數會執行給定的init_routine,不帶參數,任何后續調用都沒有效果。init_routine函數一般是初始化的程序once_control參數是一個同步結構體,需要在調用pthread_once前初始化。例如:pthread_once_tonce_control=PTHREAD_ONCE_INIT;互斥量(MutexVariables)概述?互斥量(Mutex)是“皿utualexclusion"的縮寫。互斥量是實現線程同步,和保護同時寫共享數據的主要方法互斥量對共享數據的保護就像一把鎖。在Pthreads中,任何時候僅有一個線程可以鎖定互斥量,因此,當多個線程嘗試去鎖定該互斥量時僅有一個會成功。直到鎖定互斥量的線程解鎖互斥量后,其他線程才可以去鎖定互斥量。線程必須輪著訪問受保護數據。互斥量可以防止“競爭"條件。下面的例子是一個銀行事務處理時發生了競爭條件:Thread1Thread2BalanceReadbalance:$1000$1000Readbalance:$1000$1000Deposit$200$1000Deposit$200$1000Updatebalance$1000+$200$1200Updatebalance$1000+$200$1200?上面的例子,當一個線程使用共享數據資源時,應該用一個互斥量去鎖定“Balance"。?一個擁有互斥量的線程經常用于更新全局變量。確保了多個線程更新同樣的變量以安全的方式運行,最終的結果和一個線程處理的結果是相同的。這個更新的變量屬于一個“臨界區(criticalsection)"。?使用互斥量的典型順序如下:o創建和初始一個互斥量o多個線程嘗試去鎖定該互斥量o僅有一個線程可以成功鎖定改互斥量o鎖定成功的線程做一些處理o線程解鎖該互斥量o另外一個線程獲得互斥量,重復上述過程o最后銷毀互斥量?當多個線程競爭同一個互斥量時,失敗的線程會阻塞在lock調用處。可以用“trylock”替換“lock",則失敗時不會阻塞。?當保護共享數據時,程序員有責任去確認是否需要使用互斥量。如,若四個線程會更新同樣的數據,但僅有一個線程用了互斥量,則數據可能會損壞。互斥量(MutexVariables)創建和銷毀互斥量l函數:Ipthread_mutex_init(mutex,attr)pthread_mutex_destroy(mutex)pthread_mutexattr_init(attr)pthread_mutexattr_destroy(attr)?互斥量必須用類型pthread_mutex_t類型聲明,在使用前必須初始化,這里有兩種方法可以初始化互斥量:聲明時靜態地,如:pthread_mutex_tmymutex=PTHREAD_MUTEX_INITIALIZER;動態地用pthread_mutex_init()函數,這種方法允許設定互斥量的屬性對象attr。互斥量初始化后是解鎖的。attr對象用于設置互斥量對象的屬性,使用時必須聲明為pthread_mutextattr_t類型,默認值可以是NULL。Pthreads標準定義了三種可選的互斥量屬性:o協議(Protocol):指定了協議用于阻止互斥量的優先級改變o優先級上限(Prioceiling):指定互斥量的優先級上限o進程共享(Process-shared):指定進程共享互斥量注意所有實現都提供了這三個可先的互斥量屬性。pthread_mutexattr_init()和pthread_mutexattr_destroy()函數分另U用于創建和銷毀互斥量屬性對象。pthread_mutex_destroy()應該用于釋放不需要再使用的互斥量對象。互斥量(MutexVariables)鎖定和解鎖互斥量I函數:Ipthread_mutex_lock(mutex)pthread__mutex_trylock(mutex)pthread_mutex_unlock(mutex)I用法:線程用pthread_mutex_lock()函數去鎖定指定的mutex變量,若該mutex已經被另外一個線程鎖定了,該調用將會阻塞線程直到mutex被解鎖。pthread_mutex_trylock()willattempttolockamutex.However,fthemutexisalreadylocked,theroutinewillreturnimmediatelywitha"busy"errorcode.Thisroutinemaybeusefulinpthread_mutex_trylock()嘗試著去鎖定一個互斥量,然而,若互斥量已被鎖定,程序會立刻返回并返回一個忙錯誤值。該函數在優先級改變情況下阻止死鎖是非常有用的。?線程可以用pthread_mutex_unlock()解鎖自己占用的互斥量。在一個線程完成對保護數據的使用,而其它線程要獲得互斥量在保護數據上工作時,可以調用該函數。若有一下情形則會發生錯誤:o互斥量已經被解鎖o互斥量被另一個線程占用?互斥量并沒有多么“神奇”的,實際上,它們就是參與的線程的“君子約定”。寫代碼時要確信正確地鎖定,解鎖互斥量。下面演示了一種邏輯錯誤:Thread1Thread2Thread3LockLockA=2A=A+1A=A*BUnlockUnlock?Q:有多個線程等待同一個鎖定的互斥量,當互斥量被解鎖后,那個線程會第一個鎖定互斥量?A:除非線程使用了優先級調度機制,否則,線程會被系統調度器去分配,那個線程會第一個鎖定互斥量是隨機的。例子:使用互斥量ExampleCode-UsingMutexes例程演示了線程使用互斥量處理一個點積(dotproduct)計算。主數據通過一個可全局訪問的數據結構被所有線程使用,每個線程處理數據的不同部分,主線程等待其他線程完成計算并輸出結果。#include<pthread.h>#include<stdio.h>#include<malloc.h>/*Thefollowingstructurecontainsthenecessaryinformationtoallowthefunction"dotprod"toaccessitsinputdataandplaceitsoutputintothestructure.*/typedefstruct{TOC\o"1-5"\h\zdouble *a;double *b;double sum;intveclen;}DOTDATA;/*Definegloballyaccessiblevariablesandamutex*/#defineNUMTHRDS4#defineVECLEN100DOTDATAdotstr;pthread_tcallThd[NUMTHRDS];pthread_mutex_tmutexsum;/*Thefunctiondotprodisactivatedwhenthethreadiscreated.AllinputtothisroutineisobtainedfromastructureoftypeDOTDATAandalloutputfromthisfunctioniswrittenintothisstructure.Thebenefitofthisapproachisapparentforthmulti—threadedprogram:whenathreadiscreatedwepassasingleargumenttotheactivatedfunction—typicallythisargumentisathreadnumber.Alltheotherinformationrequiredbythefunctionisaccessedfromthegloballyaccessiblestructure.*/void*dotprod(void*arg){/*Defineanduselocalvariablesforconvenience*/inti,start,end,offset,len;doublemysum,*x,*y;offset二(int)arg;len=dotstr.veclen;start二offset*len;end=start+len;x=dotstr.a;y=dotstr.b;/*Performthedotproductandassignresulttotheappropriatevariableinthestructure.*/mysum=0;for(i=start;i<end;i++){mysum+=(x[i]*y[i]);}/*Lockamutexpriortoupdatingthevalueinthesharedstrueture,andunlockituponupdating.*/pthread_mutex_loek(&mutexsum);dotstr.sum+=mysum;pthread_mutex_unlock(&mutexsum);pthread_exit((void*)0);}/*Themainprogramcreatesthreadswhichdoalltheworkandthenprintoutresultuponcompletion.Beforecreatingthethreads,theinputdataiscreated.Sinceallthreadsupdateasharedstructure,weneedamutexformutualexclusion.Themainthreadneedstowaitforallthreadstocomplete,itwaitsforeachoneofthethreads.Wespecifyathreadattributevaluethatallowthemainthreadtojoinwitthethreadsitcreates.Notealsothatwefreeuphandleswhentheyarenolongerneeded.*/intmain(intargc,char*argv[]){inti;double*a,*b;void*status;pthread_attr_tattr;/*Assignstorageandinitializevalues*/a=(double*)malloc(NUMTHRDS*VECLEN*sizeof(double));b=(double*)malloc(NUMTHRDS*VECLEN*sizeof(double));for(i=0;i<VECLEN*NUMTHRDS;i++){a[i]=1.0;b[i]=a[i];}dotstr.veclen=VECLEN;dotstr.a=a;
dotstr.b=b;dotstr.sum=0;pthread_mutex_init(&mutexsum,NULL);/*Createthreadstoperformthedotproduct*/pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);for(i=0;i<NUMTHRDS;i++){/*Eachthreadworksonadifferentsetofdata.Theoffsetisspecifiedby'i'.ThesizeofthedataforeachthreadisindicatedbyVECLEN.*/pthread_create(&callThd[i],&attr,dotprod,(void*)i)}pthread_attr_destroy(&attr);/*Waitontheotherthreads*/for(i=0;i<NUMTHRDS;i++){pthread_join(callThd[i],&status);}/*Afterjoining,printouttheresultsandcleanup*/printf("Sum=%f\n",dotstr.sum);free(a);free(b);pthread_mutex_destroy(&mutexsum);pthread_exit(NULL);}、Source、SourceSerialversionPthreadsversion條件變量(ConditionVariables)
概述?條件變量提供了另一種同步的方式。互斥量通過控制對數據的訪問實現了同步,而條件變量允許根據實際的數據值來實現同步。?沒有條件變量,程序員就必須使用線程去輪詢(可能在臨界區),查看條件是否滿足。這樣比較消耗資源,因為線程連續繁忙工作。條件變量是一種可以實現這種輪詢的方式。?條件變量往往和互斥一起使用?使用條件變量的代表性順序如下:主線程(MainThread)oooo聲明和初始化需要同步的全局數據/變量(如“oooo創建工作線程A和BThreadAThreadBo工作,一直到一定的o工作條件滿足(如“count”等于o鎖定相關互斥量一個指定的值)o改變Thread-A所等
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
評論
0/150
提交評論