多線程系列篇章計劃內(nèi)容:
iOS多線程編程(一) 多線程基礎
iOS多線程編程(二) Pthread
iOS多線程編程(三) NSThread
iOS多線程編程(四) GCD
iOS多線程編程(五) GCD的底層原理
iOS多線程編程(六) NSOperation
iOS多線程編程(七) 同步機制與鎖
iOS多線程編程(八) RunLoop
Pthreads
是操作系統(tǒng)級的線程標準。
Pthreads
是 POSIX threads 的縮寫豁陆,而 POSIX 是 Portable Operating System Interface (可移植操作系統(tǒng)接口)的縮寫踩官。
它定義了創(chuàng)建和操作線程的一套API⌒溆基于C語言實現(xiàn),使用難度較大,需要手動管理線程生命周期。
Pthreads
API中的函數(shù)調(diào)用诗充,全部是以pthread_
開頭,并可以分為四類:
-
線程管理
例如創(chuàng)建線程诱建、等待線程 (join)蝴蜓、查詢線程狀態(tài)等。 -
互斥鎖(Mutex)
創(chuàng)建、銷毀茎匠、鎖定格仲、解鎖、設置屬性等操作诵冒。 -
條件變量 (Condition Variable)
創(chuàng)建凯肋、銷毀、等待汽馋、通知侮东、設置與查詢屬性等操作。 -
同步管理
使用了互斥鎖的線程間的同步
管理
POSIX 的
semaphore
API 可以和Pthreads
協(xié)同工作豹芯,但這并不是Pthreads
的標準悄雅。因而這部分API是以“sem_
”開頭,而非“pthread_
”铁蹈。
一煤伟、Pthreads的數(shù)據(jù)類型
1.1 pthread_t
線程句柄。用于表示線程ID木缝。
出于移植目的便锨,不能把它作為整數(shù)處理,也可能是一個Structure我碟。在比較時放案,應使用函數(shù)pthread_equal()
對兩個線程ID進行比較,獲取自身所在線程ID時矫俺,使用pthread_self()
函數(shù)吱殉。
1.2 pthread_attr_t
線程屬性。
主要包括scope屬性厘托、detach屬性友雳、堆棧地址、堆棧大小铅匹、優(yōu)先級等押赊。
1.3 pthread_barrier_t
同步屏障數(shù)據(jù)類型
1.4 pthread_mutex_t
mutex數(shù)據(jù)類型
1.5 pthread_cond_t
條件變量數(shù)據(jù)類型
二、 Pthreads的函數(shù)及使用
在使用函數(shù)前需導入頭文件
#import <pthread.h>
2.1 創(chuàng)建線程
創(chuàng)建線程使用pthread_create()
函數(shù)
pthread_create (pthread_t *restrict newthread,
const pthread_attr_t *restrict attr,
void *(*start_routine) (void *),
void *restrict arg);
參數(shù)說明:
參數(shù)1:線程句柄包斑。當一個新的線程創(chuàng)建成功之后流礁,就會通過這個參數(shù)將線程的句柄返回給調(diào)用者,以便對這個線程進行管理罗丰。
參數(shù)2:線程屬性神帅。用于設置線程的屬性。這個參數(shù)是可選的萌抵,設置為NULL時找御,使用線程默認屬性元镀。
參數(shù)3:入口函數(shù)例程。線程的入口函數(shù)霎桅。如果線程創(chuàng)建成功栖疑,這個接口會返回0。
參數(shù)4:入口函數(shù)參數(shù)哆档。作為入口函數(shù)的參數(shù)蔽挠。這種設計可以在線程創(chuàng)建之前就幫他準備好一些專有數(shù)據(jù),最典型的用法就是使用C++編程時的this指針瓜浸。
示例: 創(chuàng)建線程
// 打印線程id
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid(); // 獲取進程id
tid = pthread_self(); // 獲取線程id
printf("%s pid:%lu, tid:%lu\n", s, (long unsigned)pid, (long unsigned)tid);
}
// 線程入口函數(shù)
void *threadRoutine (void *arg) {
printf( "This is a thread start routine and arg = %d.\n", *(int*)arg);
printids("new~~~");
* (int *)arg = 0; // 將傳入的參數(shù)由10修改為0
return arg;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//返回最后創(chuàng)建出來的Thread的Thread ID
pthread_t *restrict tidp;
//指定線程的attributes澳淑,可以用NULL使用默認屬性
const pthread_attr_t *restrict attr = NULL;
int arg = 10; // 入口函數(shù)參數(shù)
int thread = pthread_create(&tidp, attr, threadRoutine, &arg);
if (thread != 0) { // 返回0表示創(chuàng)建成功,否則返回正整數(shù)
NSLog(@"create thread fail");
}
int *thread_ret = NULL;
pthread_join(tidp, (void **)& thread_ret); // 等待線程tidp的完成
printf( "thread_ret = %d.\n", *thread_ret );
printids("main~~~");
}
return 0;
}
輸出結(jié)果為:
This is a thread start routine and arg = 10.
new~~~ pid:22258, tid:123145305821184
thread_ret = 0
main~~~ pid:22258, tid:4614421952
說明:
- ① 參數(shù)4可以作為線程與線程之間進行數(shù)據(jù)通訊的手段插佛,如上例中主線程向新建的子線程中傳入了參數(shù)10杠巡,在子線程可以獲取該參數(shù)。需要注意的是雇寇,這個參數(shù)類型是void * 氢拥,這樣做的目的是為了保證線程能夠接受任意類型的參數(shù),到時候再進行強制轉(zhuǎn)換就好了锨侯。
- ②
pthread_join
嫩海,它的作用是阻塞當前線程,直到合并的線程執(zhí)行完畢囚痴。第一個參數(shù)為線程句柄叁怪,第二個參數(shù)接受線程的返回值。在示例代碼中主線程等待子線程執(zhí)行完畢后才繼續(xù)執(zhí)行后面的代碼深滚。如果不添加此行代碼奕谭,程序會先執(zhí)行main~~, 再執(zhí)行new~~痴荐。并且血柳,線程線程入口函數(shù)中將入口函數(shù)參數(shù)修改為0并且return,所以打印的thread_ret 也為0生兆。
2.2 線程的合并與分離
那么pthread_join()
接口做了什么呢难捌?
2.2.1 線程的合并
首先要明確的一個問題就是什么是線程的合并。從前面的敘述中已經(jīng)了解到皂贩,pthread_create()
接口負責創(chuàng)建了一個線程栖榨。那么線程也屬于系統(tǒng)的資源,這跟內(nèi)存沒什么兩樣明刷,而且線程本身也要占據(jù)一定的內(nèi)存空間。
眾所周知的一個問題就是C/C++編程中如果要通過malloc()
或new
分配了一塊內(nèi)存满粗,就必須使用free()
或delete
來回收這塊內(nèi)存辈末,否則就會產(chǎn)生著名的內(nèi)存泄漏問題。
既然線程和內(nèi)存沒什么兩樣,那么有創(chuàng)建就必須得有回收挤聘,否則就會產(chǎn)生另外一個著名的資源泄漏問題轰枝,這同樣也是一個嚴重的問題。那么線程的合并就是回收線程資源了组去。
線程的合并是一種主動回收線程資源的方案。當一個進程或線程調(diào)用了針對其它線程的pthread_join()
接口,就是線程合并了客们。這個接口會阻塞調(diào)用進程或線程果复,直到被合并的線程結(jié)束為止。當被合并線程結(jié)束键闺,pthread_join()
接口就會回收這個線程的資源寿烟,并將這個線程的返回值返回給合并者。
2.2.2 線程的分離
與線程合并相對應的另外一種線程資源回收機制是線程分離辛燥,調(diào)用接口是pthread_detach()
筛武。
線程分離是將線程資源的回收工作交由系統(tǒng)自動來完成,也就是說當被分離的線程結(jié)束之后挎塌,系統(tǒng)會自動回收它的資源徘六。因為線程分離是啟動系統(tǒng)的自動回收機制,那么程序也就無法獲得被分離線程的返回值榴都,這就使得pthread_detach()
接口只要擁有一個參數(shù)就行了待锈,那就是被分離線程句柄。
線程合并和線程分離都是用于回收線程資源的缭贡,可以根據(jù)不同的業(yè)務場景酌情使用炉擅。不管有什么理由,你都必須選擇其中一種阳惹,否則就會引發(fā)資源泄漏的問題谍失,這個問題與內(nèi)存泄漏同樣可怕。
2.3 線程的屬性
線程是有屬性的莹汤,這個屬性由一個線程屬性對象來描述快鱼。線程屬性對象由pthread_attr_init()
接口初始化,并由pthread_attr_destory()
來銷毀纲岭,它們的完整定義是:
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);
線程擁有哪些屬性呢抹竹?
一般地,Linux下的線程有:綁定屬性止潮、分離屬性窃判、調(diào)度屬性、堆棧大小屬性和滿占警戒區(qū)大小屬性喇闸。
2.3.1 綁定屬性(scope)
說到這個綁定屬性袄琳,就不得不提起另外一個概念:輕進程(Light Weight Process询件,簡稱LWP)。
輕進程和Linux系統(tǒng)的內(nèi)核線程擁有相同的概念唆樊,屬于內(nèi)核的調(diào)度實體宛琅。一個輕進程可以控制一個或多個線程。
在計算機操作系統(tǒng)中,輕量級進程(LWP)是一種實現(xiàn)多任務的方法逗旁。與普通進程相比嘿辟,LWP與其他進程共享所有(或大部分)它的邏輯地址空間和系統(tǒng)資源;與線程相比片效,LWP有它自己的進程標識符红伦,并和其他進程有著父子關系;這是和類Unix操作系統(tǒng)的系統(tǒng)調(diào)用vfork()生成的進程一樣的堤舒。另外色建,線程既可由應用程序管理,又可由內(nèi)核管理舌缤,而LWP只能由內(nèi)核管理并像普通進程一樣被調(diào)度箕戳。Linux內(nèi)核是支持LWP的典型例子。
默認情況下国撵,對于一個擁有n個線程的程序陵吸,啟動多少輕進程,由哪些輕進程來控制哪些線程由操作系統(tǒng)
來控制介牙,這種狀態(tài)被稱為非綁定
的壮虫。那么綁定的含義就很好理解了,只要指定了某個線程“綁”在某個輕進程上环础,就可以稱之為綁定
的囚似。
被綁定的線程具有較高的響應速度,因為操作系統(tǒng)的調(diào)度主體是輕進程线得,綁定線程可以保證在需要的時候它總有一個輕進程可用饶唤。綁定屬性就是干這個用的。
設置綁定屬性的接口是pthread_attr_setscope()
贯钩,它的完整定義是:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
它有兩個參數(shù)募狂,第一個就是線程屬性對象的指針,第二個就是綁定類型角雷,擁有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)祸穷。下面代碼演示了這個屬性的使用。
示例:設置線程綁定屬性
……
int main( int argc, char *argv[] )
{
pthread_attr_t attr;
pthread_t th;
……
pthread_attr_init( &attr );
pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
pthread_create( &th, &attr, thread, NULL );
……
}
值得注意的是:
Linux的線程永遠都是綁定的勺三,所以PTHREAD_SCOPE_PROCESS在Linux中不管用雷滚,而且會返回ENOTSUP錯誤。如果只是在Linux下編寫多線程程序吗坚,可以完全忽略這個屬性揭措。
2.3.2 分離屬性(detach)
表示新線程是否與進程中其他線程脫離同步炊汹。
線程能夠被合并和分離伴找,分離屬性就是讓線程在創(chuàng)建之前就決定它應該是分離的。如果設置為PTHREAD_CREATE_DETACHED孽文,就沒有必要調(diào)用pthread_join()
或pthread_detach()
來回收線程資源了,在退出時自行釋放所占用的資源巷屿。
設置分離屬性的接口是pthread_attr_setdetachstate()
,它的完整定義是:
pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
它的第二個參數(shù)有兩個取值:PTHREAD_CREATE_DETACHED(分離的)和PTHREAD_CREATE_JOINABLE(可合并的绪杏,也是默認屬性)。下面代碼演示了這個屬性的使用。
示例:設置線程分離屬性
……
int main( int argc, char *argv[] )
{
pthread_attr_t attr;
pthread_t th;
……
pthread_attr_init( &attr );
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create( &th, &attr, thread, NULL );
……
}
2.3.3 調(diào)度屬性
線程的調(diào)度屬性有三個,分別是:算法污淋、優(yōu)先級和繼承權顶滩。
算法(schedpolicy)
Linux提供的線程調(diào)度算法有三個:輪詢
、先進先出
和其它
寸爆。
其中輪詢和先進先出調(diào)度算法是POSIX標準所規(guī)定礁鲁,而其他則代表采用Linux自己認為更合適的調(diào)度算法,所以默認的調(diào)度算法也就是其它了赁豆。
輪詢和先進先出調(diào)度算法都屬于實時調(diào)度算法仅醇。
輪詢指的是時間片輪轉(zhuǎn),當線程的時間片用完魔种,系統(tǒng)將重新分配時間片析二,并將它放置在就緒隊列尾部,這樣可以保證具有相同優(yōu)先級的輪詢?nèi)蝿斋@得公平的CPU占用時間;
先進先出就是先到先服務叶摄,一旦線程占用了CPU則一直運行属韧,直到有更高優(yōu)先級的線程出現(xiàn)或自己放棄。
設置線程調(diào)度算法的接口是pthread_attr_setschedpolicy()
蛤吓,它的完整定義是:
pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
它的第二個參數(shù)有三個取值:SCHED_RR(實時宵喂、輪轉(zhuǎn)法)、SCHED_FIFO(實時柱衔、先入先出)和SCHED_OTHER(正常樊破、非實時)。
優(yōu)先級
Linux的線程優(yōu)先級與進程的優(yōu)先級不一樣唆铐。
Linux的線程優(yōu)先級是從1到99的數(shù)值,數(shù)值越大代表優(yōu)先級越高奔滑。
而且要注意的是艾岂,只有采用SHCED_RR或SCHED_FIFO調(diào)度算法時,優(yōu)先級才有效朋其。對于采用SCHED_OTHER調(diào)度算法的線程王浴,其優(yōu)先級恒為0。
設置線程優(yōu)先級的接口是pthread_attr_setschedparam()
梅猿,它的完整定義是:
struct sched_param {
int sched_priority;
}
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
sched_param
結(jié)構(gòu)體的sched_priority
字段就是線程的優(yōu)先級了氓辣。
此外,即便采用SCHED_RR或SCHED_FIFO調(diào)度算法袱蚓,線程優(yōu)先級也不是隨便就能設置的钞啸。首先,進程必須是以root賬號運行的喇潘;其次体斩,還需要放棄線程的繼承權。什么是繼承權呢颖低?
繼承權(inheritsched)
繼承權就是當創(chuàng)建新的線程時絮吵,新線程要繼承父線程(創(chuàng)建者線程)的調(diào)度屬性。如果不希望新線程繼承父線程的調(diào)度屬性忱屑,就要放棄繼承權蹬敲。
設置線程繼承權的接口是pthread_attr_setinheritsched()
辆憔,它的完整定義是:
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
它的第二個參數(shù)有兩個取值:PTHREAD_EXPLICIT_SCHED(放棄繼承權)和 PTHREAD_INHERIT_SCHED(擁有繼承權)细诸。前者表示新線程使用顯式指定調(diào)度策略和調(diào)度參數(shù)(即attr中的值)灶泵,而后者表示繼承調(diào)用者線程的值夷野。新線程在默認情況下是擁有繼承權煎楣。
下面代碼能夠演示不同調(diào)度算法和不同優(yōu)先級下各線程的行為滴某,同時也展示如何修改線程的調(diào)度屬性狈孔。
示例:設置線程調(diào)度屬性
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_COUNT 12
void show_thread_policy( int threadno )
{
int policy;
struct sched_param param;
pthread_getschedparam( pthread_self(), &policy, param );
switch( policy ){
case SCHED_OTHER:
printf( "SCHED_OTHER %d\n", threadno );
break;
case SCHED_RR:
printf( "SCHDE_RR %d\n", threadno );
break;
case SCHED_FIFO:
printf( "SCHED_FIFO %d\n", threadno );
break;
default:
printf( "UNKNOWN\n");
}
}
void* thread( void *arg )
{
int i, j;
long threadno = (long)arg;
printf( "thread %d start\n", threadno );
sleep(1);
show_thread_policy( threadno );
for( i = 0; i < 10; ++i ) {
for( j = 0; j < 100000000; ++j ){}
printf( "thread %d\n", threadno );
}
printf( "thread %d exit\n", threadno );
return NULL;
}
int main( int argc, char *argv[] )
{
long i;
pthread_attr_t attr[THREAD_COUNT];
pthread_t pth[THREAD_COUNT];
struct sched_param param;
for( i = 0; i < THREAD_COUNT; ++i )
pthread_attr_init( &attr[i] );
for( i = 0; i < THREAD_COUNT / 2; ++i ) {
param.sched_priority = 10;
pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
pthread_attr_setschedparam( &attr[i], param );
pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
}
for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {
param.sched_priority = 20;
pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
pthread_attr_setschedparam( &attr[i], param );
pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
}
for( i = 0; i < THREAD_COUNT; ++i )
pthread_create( &pth[i], &attr[i], thread, (void*)i );
for( i = 0; i < THREAD_COUNT; ++i )
pthread_join( pth[i], NULL );
for( i = 0; i < THREAD_COUNT; ++i )
pthread_attr_destroy( &attr[i] );
return 0;
}
2.3.4 堆棧大小屬性
線程的主函數(shù)與程序的主函數(shù)main()
有一個很相似的特性珠插,那就是可以擁有局部變量
食店。雖然同一個進程的線程之間是共享內(nèi)存空間的渣淤,但是它的局部變量確并不共享赏寇。原因就是局部變量存儲在堆棧中,而不同的線程擁有不同的堆棧价认。Linux系統(tǒng)為每個線程默認分配了8MB的堆椥岫ǎ空間,如果覺得這個空間不夠用用踩,可以通過修改線程的堆棧大小屬性進行擴容渠退。
修改線程堆棧大小屬性的接口是pthread_attr_setstacksize()
,它的完整定義為:
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
它的第二個參數(shù)就是堆棧大小了脐彩,以字節(jié)為單位碎乃。需要注意的是,線程堆棧不能小于16KB惠奸,而且盡量按4KB(32位系統(tǒng))或2MB(64位系統(tǒng))的整數(shù)倍分配梅誓,也就是內(nèi)存頁面大小的整數(shù)倍。此外佛南,修改線程堆棧大小是有風險的梗掰,如果你不清楚你在做什么,最好別動它嗅回。
2.3.5 滿棧警戒區(qū)屬性
既然線程是有堆棧的及穗,而且還有大小限制,那么就一定會出現(xiàn)將堆棧用滿的情況绵载。線程的堆棧用滿是非常危險的事情埂陆,因為這可能會導致對內(nèi)核空間的破壞,一旦被有心人士所利用尘分,后果也不堪設想猜惋。為了防治這類事情的發(fā)生,Linux為線程堆棧設置了一個滿棧警戒區(qū)
培愁。這個區(qū)域一般就是一個頁面著摔,屬于線程堆棧的一個擴展區(qū)域。一旦有代碼訪問了這個區(qū)域定续,就會發(fā)出SIGSEGV信號進行通知谍咆。
雖然滿棧警戒區(qū)可以起到安全作用,但是也有弊病私股,就是會白白浪費掉內(nèi)存空間摹察,對于內(nèi)存緊張的系統(tǒng)會使系統(tǒng)變得很慢。所有就有了關閉這個警戒區(qū)的需求倡鲸。同時供嚎,如果我們修改了線程堆棧的大小,那么系統(tǒng)會認為我們會自己管理堆棧,也會將警戒區(qū)取消掉克滴,如果有需要就要開啟它逼争。
修改滿棧警戒區(qū)屬性的接口是pthread_attr_setguardsize()
,它的完整定義為:
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
它的第二個參數(shù)就是警戒區(qū)大小劝赔,以字節(jié)為單位誓焦。與設置線程堆棧大小屬性相仿,應該盡量按照4KB或2MB的整數(shù)倍來分配着帽。當設置警戒區(qū)大小為0時杂伟,就關閉了這個警戒區(qū)。
雖然棧滿警戒區(qū)需要浪費掉一點內(nèi)存仍翰,但是能夠極大的提高安全性赫粥,所以這點損失是值得的。而且一旦修改了線程堆棧的大小歉备,一定要記得同時設置這個警戒區(qū)傅是。
2.4 線程的終止
2.4.1 線程的終止
線程退出的條件,下面任一種都可以:
- 1.線程正常執(zhí)行完畢后返回
- 2.調(diào)用
pthread_exit
函數(shù)退出
void pthread_exit(void * rval_ptr); // rval_ptr指向返回值
- 3.線程被同一進程的其他線程
pthread_cancel
void pthread_cancel(pthread_t tid)
pthrea_cancel
不等待線程終止蕾羊,而是提出請求。該函數(shù)會使指定線程如同調(diào)用了pthread_exit(PTHREAD_CANCELLED)帽驯。不過龟再,指定線程可以選擇忽略或者進行自己的處理。此外尼变,該函數(shù)不會導致Block利凑,只是發(fā)送Cancel這個請求。
- 4.創(chuàng)建線程的進程
exec()
或exit()
- 5.
main()
先完成嫌术,且沒有顯示調(diào)用pthread_exit
哀澈。
如果沒有顯式地調(diào)用 pthread_exit()
, main()
就會在它產(chǎn)生的線程之前完成度气,那么所有線程都將終止割按。
顯示調(diào)用 pthread_exit()
,則main()
會在結(jié)束前等待所有線程執(zhí)行完畢磷籍。
示例:線程終止
// 打印線程id
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid:%lu, tid:%lu\n", s, (long unsigned)pid, (long unsigned)tid);
}
// 線程return
void *return_thread(void * arg)
{
printids("thread returning~~~");
return (void*)0;
}
// 線程exit
void *exit_thread(void * arg)
{
printids("thread exiting~~~");
pthread_exit((void*) 2); // 參數(shù)可以返回結(jié)構(gòu)體适荣,但是這個結(jié)構(gòu)體必須返回后還能使用(不是在棧上分配)
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int error;
pthread_t *restrict tidp1, tidp2;
void * rVal;
error = pthread_create(&tidp1, NULL, return_thread, NULL);
if (error != 0) {
NSLog(@"create thread - tidp1 fail");
}
error = pthread_create(&tidp2, NULL, exit_thread, NULL);
if (error != 0) {
NSLog(@"create thread - tidp2 fail");
}
pthread_join(tidp1, &rVal);
printf("return_thread return:%ld\n", (long)rVal);
pthread_join(tidp2, &rVal);
printf("exit_thread return:%ld\n", (long)rVal);
printids("main~~~");
}
return 0;
}
輸出結(jié)果為:
thread returning~~~ pid:23857, tid:123145461620736
thread exiting~~~ pid:23857, tid:123145462157312
return_thread return:0
exit_thread return:2
main~~~ pid:23857, tid:4382375360
2.4.2 線程的清理
線程可以安排在它退出的時候,某些函數(shù)自動被調(diào)用院领,類似atexit()
函數(shù)弛矛。 需要調(diào)用如下函數(shù):
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
這兩個函數(shù)維護一個函數(shù)指針的Stack,可以把函數(shù)指針和函數(shù)參數(shù)值push/pop比然。執(zhí)行的順序則是從棧頂?shù)綏5渍擅ィ簿褪呛蚿ush的順序相反。
在下面情況下pthread_cleanup_push
所指定的thread cleanup handlers
會被調(diào)用:
- a.調(diào)用
pthread_exit
- b.響應
cancel
請求 - c.以非0參數(shù)調(diào)用
pthread_cleanup_pop()
。(如果以0調(diào)用pthread_cleanup_pop()万俗,那么handler不會被調(diào)用湾笛,只是刪除清理函數(shù)。
void *thread_func(void *arg)
{
pthread_cleanup_push(cleanup, “handler”)
// do something
Pthread_cleanup_pop(0);
return((void *)0)该编;
}
2.5 線程本地存儲
內(nèi)線程之間可以共享內(nèi)存地址空間迄本,線程之間的數(shù)據(jù)交換可以非常快捷课竣,這是線程最顯著的優(yōu)點嘉赎。但是多個線程訪問共享數(shù)據(jù),需要昂貴的同步開銷
于樟,也容易造成與同步相關的BUG公条,更麻煩的是有些數(shù)據(jù)根本就不希望被共享。
C程序庫中的errno是個最典型的一個例子迂曲。errno是一個全局變量靶橱,會保存最后一個系統(tǒng)調(diào)用的錯誤代碼。在單線程環(huán)境并不會出現(xiàn)什么問題路捧。但是在多線程環(huán)境关霸,由于所有線程都會有可能修改errno,這就很難確定errno代表的到底是哪個系統(tǒng)調(diào)用的錯誤代碼了杰扫。這就是有名的“非線程安全(Non Thread-Safe)”的队寇。
此外,從現(xiàn)代技術角度看章姓,在很多時候使用多線程的目的并不是為了對共享數(shù)據(jù)進行并行處理佳遣。更多是由于多核心CPU
技術的引入,為了充分利用CPU
資源而進行并行運算(不互相干擾)凡伊。換句話說零渐,大多數(shù)情況下每個線程只會關心自己的數(shù)據(jù)而不需要與別人同步。
為了解決這些問題系忙,可以有很多種方案诵盼。比如使用不同名稱的全局變量。但是像errno這種名稱已經(jīng)固定了的全局變量就沒辦法了笨觅。在前面的內(nèi)容中提到在線程堆棧中分配局部變量是不在線程間共享的拦耐。但是它有一個弊病,就是線程內(nèi)部的其它函數(shù)很難訪問到见剩。
目前解決這個問題的簡便易行的方案是線程本地存儲杀糯,即Thread Local Storage,簡稱TLS苍苞。利用TLS固翰,errno所反映的就是本線程內(nèi)最后一個系統(tǒng)調(diào)用的錯誤代碼了狼纬,也就是線程安全的了。
Linux提供了對TLS的完整支持骂际,通過下面這些接口來實現(xiàn):
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_key_delete(pthread_key_t key);
void* pthread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
-
pthread_key_create()
接口用于創(chuàng)建一個線程本地存儲區(qū)疗琉。
- 第一個參數(shù)用來返回這個存儲區(qū)的句柄,需要使用一個全局變量保存歉铝,以便所有線程都能訪問到盈简。
- 第二個參數(shù)是線程本地數(shù)據(jù)的回收函數(shù)指針岂座,如果希望自己控制線程本地數(shù)據(jù)的生命周期诫给,這個參數(shù)可以傳遞NULL。
-
pthread_key_delete()
接口用于回收線程本地存儲區(qū)雷滋。其唯一的參數(shù)就要回收的存儲區(qū)的句柄类缤。 -
pthread_getspecific()
和pthread_setspecific()
這兩個接口分別用于獲取和設置線程本地存儲區(qū)的數(shù)據(jù)臼勉。這兩個接口在不同的線程下會有不同的結(jié)果(相同的線程下就會有相同的結(jié)果),這也就是線程本地存儲的關鍵所在餐弱。
下面代碼展示了如何在Linux使用線程本地存儲宴霸,注意執(zhí)行結(jié)果,分析一下線程本地存儲的一些特性膏蚓,以及內(nèi)存回收的時機瓢谢。
示例:使用線程本地存儲
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_COUNT 10
pthread_key_t g_key;
typedef struct thread_data{
int thread_no;
} thread_data_t;
void show_thread_data()
{
thread_data_t *data = pthread_getspecific( g_key );
printf( "Thread %d \n", data->thread_no );
}
void* thread( void *arg )
{
thread_data_t *data = (thread_data_t *)arg;
printf( "Start thread %d\n", data->thread_no );
pthread_setspecific( g_key, data );
show_thread_data();
printf( "Thread %d exit\n", data->thread_no );
}
void free_thread_data( void *arg )
{
thread_data_t *data = (thread_data_t*)arg;
printf( "Free thread %d data\n", data->thread_no );
free( data );
}
int main( int argc, char *argv[] )
{
int i;
pthread_t pth[THREAD_COUNT];
thread_data_t *data = NULL;
pthread_key_create( &g_key, free_thread_data );
for( i = 0; i < THREAD_COUNT; ++i ) {
data = malloc( sizeof( thread_data_t ) );
data->thread_no = i;
pthread_create( &pth[i], NULL, thread, data );
}
for( i = 0; i < THREAD_COUNT; ++i )
pthread_join( pth[i], NULL );
pthread_key_delete( g_key );
return 0;
}
2.6 線程的同步
雖然線程本地存儲可以避免線程訪問共享數(shù)據(jù),但是線程之間的大部分數(shù)據(jù)始終還是共享的驮瞧。在涉及到對共享數(shù)據(jù)進行讀寫操作時恩闻,就必須使用同步機制,否則就會造成線程們哄搶共享數(shù)據(jù)的結(jié)果剧董,這會把你的數(shù)據(jù)弄的七零八落理不清頭緒。
Linux提供的線程同步機制主要有互斥鎖
和條件變量
破停。
2.6.1 互斥鎖
首先我們看一下互斥鎖翅楼。所謂的互斥就是線程之間互相排斥,獲得資源的線程排斥其它沒有獲得資源的線程真慢。Linux使用互斥鎖來實現(xiàn)這種機制毅臊。
既然叫鎖,就有加鎖和解鎖的概念黑界。當線程獲得了加鎖的資格管嬉,那么它將獨享這個鎖,其它線程一旦試圖去碰觸這個鎖就立即被系統(tǒng)“拍暈”朗鸠。當加鎖的線程解開并放棄了這個鎖之后蚯撩,那些被“拍暈”的線程會被系統(tǒng)喚醒,然后繼續(xù)去爭搶這個鎖烛占。至于誰能搶到胎挎,只有天知道沟启。但是總有一個能搶到。于是其它來湊熱鬧的線程又被系統(tǒng)給“拍暈”了……如此反復犹菇。
從互斥鎖的這種行為看德迹,線程加鎖和解鎖之間的代碼相當于一個獨木橋,同一時刻只有一個線程能執(zhí)行揭芍。從全局上看胳搞,在這個地方,所有并行運行的線程都變成了排隊運行了称杨。比較專業(yè)的叫法是同步執(zhí)行肌毅,這段代碼區(qū)域叫臨界區(qū)
。同步執(zhí)行就破壞了線程并行性的初衷了列另,臨界區(qū)越大破壞得越厲害芽腾。所以在實際應用中,應該盡量避免有臨界區(qū)出現(xiàn)页衙。實在不行摊滔,臨界區(qū)也要盡量的小。如果連縮小臨界區(qū)都做不到店乐,那還使用多線程干嘛艰躺?
初始化和銷毀互斥鎖的接口是pthread_mutex_init()
和pthead_mutex_destroy()
,
對于加鎖和解鎖則有pthread_mutex_lock()
眨八、pthread_mutex_trylock()
和pthread_mutex_unlock()
腺兴。
這些接口的完整定義如下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destory(pthread_mutex_t *mutex );
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
從這些定義中可以看到,互斥鎖也是有屬性的廉侧。只不過這個屬性在絕大多數(shù)情況下都不需要改動页响,所以使用默認的屬性就行。方法就是給它傳遞NULL段誊。
phtread_mutex_trylock()
比較特別闰蚕,用它試圖加鎖的線程永遠都不會被系統(tǒng)“拍暈”,只是通過返回EBUSY來告訴程序員這個鎖已經(jīng)有人用了连舍。至于是否繼續(xù)“強闖”臨界區(qū)没陡,則由程序員決定。系統(tǒng)提供這個接口的目的可不是讓線程“強闖”臨界區(qū)的索赏。它的根本目的還是為了提高并行性盼玄,留著這個線程去干點其它有意義的事情。當然潜腻,如果很幸運恰巧這個時候還沒有人擁有這把鎖埃儿,那么自然也會取得臨界區(qū)的使用權。
下面代碼演示了在Linux下如何使用互斥鎖砾赔。
示例: 使用互斥鎖
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t g_mutex;
int g_lock_var = 0;
void* thread1( void *arg )
{
int i, ret;
time_t end_time;
end_time = time(NULL) + 10;
while( time(NULL) < end_time ) {
ret = pthread_mutex_trylock( &g_mutex );
if( EBUSY == ret ) {
printf( "thread1: the varible is locked by thread2.\n" );
} else {
printf( "thread1: lock the variable!\n" );
++g_lock_var;
pthread_mutex_unlock( &g_mutex );
}
sleep(1);
}
return NULL;
}
void* thread2( void *arg )
{
int i;
time_t end_time;
end_time = time(NULL) + 10;
while( time(NULL) < end_time ) {
pthread_mutex_lock( &g_mutex );
printf( "thread2: lock the variable!\n" );
++g_lock_var;
sleep(1);
pthread_mutex_unlock( &g_mutex );
}
return NULL;
}
int main( int argc, char *argv[] )
{
int i;
pthread_t pth1,pth2;
pthread_mutex_init( &g_mutex, NULL );
pthread_create( &pth1, NULL, thread1, NULL );
pthread_create( &pth2, NULL, thread2, NULL );
pthread_join( pth1, NULL );
pthread_join( pth2, NULL );
pthread_mutex_destroy( &g_mutex );
printf( "g_lock_var = %d\n", g_lock_var );
return 0;
}
最后需要補充一點蝌箍,互斥鎖在同一個線程內(nèi)青灼,沒有互斥的特性。也就是說妓盲,線程不能利用互斥鎖讓系統(tǒng)將自己“拍暈”杂拨。解釋這個現(xiàn)象的一個很好的理由就是,擁有鎖的線程把自己“拍暈”了悯衬,誰還能再擁有這把鎖呢弹沽?但是另外情況需要避免,就是兩個線程已經(jīng)各自擁有一把鎖了筋粗,但是還想得到對方的鎖策橘,這個時候兩個線程都會被“拍暈”。一旦這種情況發(fā)生娜亿,就誰都不能獲得這個鎖了丽已,這種情況還有一個著名的名字——死鎖
。死鎖是永遠都要避免的事情买决,因為這是嚴重損人不利己的行為沛婴。
2.6.2 條件變量
條件變量關鍵點在“變量”上。與鎖的不同之處就是督赤,當線程遇到這個“變量”嘁灯,并不是類似鎖那樣的被系統(tǒng)給“拍暈”,而是根據(jù)“條件”來選擇是否在那里等待躲舌。等待什么呢丑婿?等待允許通過的“信號”。這個“信號”是系統(tǒng)控制的嗎没卸?顯然不是羹奉!它是由另外一個線程來控制的。
如果說互斥鎖可以比作獨木橋约计,那么條件變量這就好比是馬路上的紅綠燈尘奏。車輛遇到紅綠燈肯定會根據(jù)“燈”的顏色來判斷是否通行,那么誰來控制“燈”的顏色呢病蛉?一定是交警啊,至少你我都不敢動它(有人會說那是自動的瑰煎,可是間隔多少時間變換也是交警設置不是铺然?)。那么“車輛”和“交警”就是馬路上的兩類線程酒甸,大多數(shù)情況下都是“車”多“交警”少魄健。
更深一步理解,條件變量是一種事件機制插勤。由一類線程來控制“事件”的發(fā)生沽瘦,另外一類線程等待“事件”的發(fā)生革骨。為了實現(xiàn)這種機制,條件變量必須是共享于線程之間的全局變量析恋。而且良哲,條件變量
也需要與互斥鎖同時使用
。
初始化和銷毀條件變量的接口是pthread_cond_init()
和pthread_cond_destory()
;
控制“事件”發(fā)生的接口是pthread_cond_signal()
或pthread_cond_broadcast()
助隧;
等待“事件”發(fā)生的接口是pthead_cond_wait()
或pthread_cond_timedwait()
筑凫。
它們的完整定義如下:
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destory(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const timespec *abstime);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
對于等待“事件”的接口從其名稱中可以看出,一種是無限期等待
并村,一種是限時等待
巍实。后者與互斥鎖的pthread_mutex_trylock()
有些類似,即當?shù)却摹笆录苯?jīng)過一段時間之后依然沒有發(fā)生哩牍,那就去干點別的有意義的事情去棚潦。
而對于控制“事件”發(fā)生的接口則有“單播”和“廣播”之說。所謂單播就是只有一個線程會得到“事件”已經(jīng)發(fā)生了的“通知”膝昆,而廣播就是所有線程都會得到“通知”丸边。對于廣播情況,所有被“通知”到的線程也要經(jīng)過由互斥鎖
控制的獨木橋外潜。
對于條件變量的使用原环,可以參考如下代碼,它實現(xiàn)了一種生產(chǎn)者與消費者的線程同步方案处窥。
示例 : 使用條件變量
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define BUFFER_SIZE 5
pthread_mutex_t g_mutex;
pthread_cond_t g_cond;
typedef struct {
char buf[BUFFER_SIZE];
int count;
} buffer_t;
buffer_t g_share = {"", 0};
char g_ch = 'A';
void* producer( void *arg )
{
printf( "Producer starting.\n" );
while( g_ch != 'Z' ) {
pthread_mutex_lock( &g_mutex );
if( g_share.count < BUFFER_SIZE ) {
g_share.buf[g_share.count++] = g_ch++;
printf( "Prodcuer got char[%c]\n", g_ch - 1 );
if( BUFFER_SIZE == g_share.count ) {
printf( "Producer signaling full.\n" );
pthread_cond_signal( &g_cond );
}
}
pthread_mutex_unlock( &g_mutex );
}
printf( "Producer exit.\n" );
return NULL;
}
void* consumer( void *arg )
{
int i;
printf( "Consumer starting.\n" );
while( g_ch != 'Z' ) {
pthread_mutex_lock( &g_mutex );
printf( "Consumer waiting\n" );
pthread_cond_wait( &g_cond, &g_mutex );
printf( "Consumer writing buffer\n" );
for( i = 0; g_share.buf[i] && g_share.count; ++i ) {
putchar( g_share.buf[i] );
--g_share.count;
}
putchar('\n');
pthread_mutex_unlock( &g_mutex );
}
printf( "Consumer exit.\n" );
return NULL;
}
int main( int argc, char *argv[] )
{
pthread_t ppth, cpth;
pthread_mutex_init( &g_mutex, NULL );
pthread_cond_init( &g_cond, NULL );
pthread_create( &cpth, NULL, consumer, NULL );
pthread_create( &ppth, NULL, producer, NULL );
pthread_join( ppth, NULL );
pthread_join( cpth, NULL );
pthread_mutex_destroy( &g_mutex );
pthread_cond_destroy( &g_cond );
return 0;
}
這段代碼存在一個潛在的問題:
如果producer線程并行執(zhí)行的比consumer快嘱吗,producer線程會先獲取鎖,之后向consumer發(fā)出信號滔驾,但此時consumer沒辦法獲取鎖谒麦,也就執(zhí)行不到pthead_cond_wait()
處,那么程序就陷入尷尬的境地哆致,發(fā)生死鎖绕德。
簡單的,可以在pthread_create( &cpth, NULL, consumer, NULL )
; 和pthread_create( &ppth, NULL, producer, NULL )
; 之間加入一個長的延時函數(shù)usleep(100)摊阀,確保consumer線程先行執(zhí)行到pthead_cond_wait()
處耻蛇。
從代碼中會發(fā)現(xiàn),等待“事件”發(fā)生的接口都需要傳遞一個互斥鎖
給它胞此。而實際上這個互斥鎖還要在調(diào)用它們之前加鎖
臣咖,調(diào)用之后解鎖
。不單如此漱牵,在調(diào)用操作“事件”發(fā)生的接口之前也要加鎖夺蛇,調(diào)用之后解鎖。這就面臨一個問題酣胀,按照這種方式刁赦,等于“發(fā)生事件”和“等待事件”是互為臨界區(qū)的烦却。也就是說阅仔,如果“事件”還沒有發(fā)生谍椅,那么有線程要等待這個“事件”就會阻止“事件”的發(fā)生勉盅。更干脆一點,就是這個“生產(chǎn)者”和“消費者”是在來回的走獨木橋宦焦。但是實際的情況是发钝,“消費者”在緩沖區(qū)滿的時候會得到這個“事件”的“通知”,然后將字符逐個打印出來波闹,并清理緩沖區(qū)酝豪。直到緩沖區(qū)的所有字符都被打印出來之后,“生產(chǎn)者”才開始繼續(xù)工作精堕。
為什么會有這樣的結(jié)果呢孵淘?這就要說明一下pthread_cond_wait()
接口對互斥鎖做什么。答案是:解鎖
歹篓。pthread_cond_wait()
首先會解鎖互斥鎖
瘫证,然后進入等待
。這個時候“生產(chǎn)者”就能夠進入臨界區(qū)
庄撮,然后在條件滿足的時候向“消費者”發(fā)出信號背捌。
當pthead_cond_wait()
獲得“通知”之后,它還要對互斥鎖加鎖
洞斯,這樣可以防止“生產(chǎn)者”繼續(xù)工作而“撐壞”緩沖區(qū)毡庆。另外,“生產(chǎn)者”在緩沖區(qū)不滿的情況下才能工作的這個限定條件是很有必要的烙如。因為在pthread_cond_wait()
獲得通知之后么抗,在沒有對互斥鎖加鎖
之前,“生產(chǎn)者”可能已經(jīng)重新進入臨界區(qū)
了亚铁,這樣“消費者”又被堵住了蝇刀。也就是因為條件變量這種工作性質(zhì),導致它必須與互斥鎖聯(lián)合使用徘溢。
此外吞琐,利用條件變量和互斥鎖,可以模擬出很多其它類型的線程同步機制然爆,比如:event
顽分、semaphore
等。
附:Pthreads常見函數(shù)釋義
線程操作函數(shù)
pthread_create():創(chuàng)建一個線程
pthread_exit():終止當前線程
pthread_cancel():請求中斷另外一個線程的運行施蜜。被請求中斷的線程會繼續(xù)運行,直至到達某個取消點雌隅。取消點是線程檢查是否被取消并按照請求進行動作的一個位置翻默。POSIX 的取消類型有兩種缸沃,一種是
延遲取消
(PTHREAD_CANCEL_DEFERRED),這是系統(tǒng)默認的取消類型修械,即在線程到達取消點之前趾牧,不會出現(xiàn)真正的取消;另外一種是異步取消
(PHREAD_CANCEL_ASYNCHRONOUS)肯污,使用異步取消時翘单,線程可以在任意時間取消。系統(tǒng)調(diào)用的取消點實際上是函數(shù)中取消類型被修改為異步取消至修改回延遲取消的時間段蹦渣。幾乎可以使線程掛起的庫函數(shù)都會響應CANCEL信號哄芜,終止線程,包括sleep
柬唯、delay
等延時函數(shù)认臊。pthread_join():阻塞當前的線程,直到另外一個線程運行結(jié)束
pthread_kill():向指定ID的線程發(fā)送一個[信號]锄奢,如果線程不處理該信號失晴,則按照信號默認的行為作用于整個進程。信號值0為保留信號拘央,作用是根據(jù)函數(shù)的返回值判斷線程是不是還活著涂屁。
pthread_cleanup_push():線程可以安排異常退出時需要調(diào)用的函數(shù),這樣的函數(shù)稱為
線程清理程序
灰伟,線程可以建立多個清理程序拆又。線程清理程序的入口地址使用棧保存,實行先進后處理
原則袱箱。由pthread_cancel
或pthread_exit
引起的線程結(jié)束遏乔,會次序執(zhí)行由pthread_cleanup_push
壓入的函數(shù)。線程函數(shù)執(zhí)行return
語句返回不會引起線程清理程序被執(zhí)行发笔。pthread_cleanup_pop():以非0參數(shù)調(diào)用時盟萨,引起當前被彈出的線程清理程序執(zhí)行。
pthread_setcancelstate():允許或禁止取消另外一個線程的運行了讨。
pthread_setcanceltype():設置線程的取消類型為延遲取消或異步取消捻激。
線程屬性函數(shù)
pthread_attr_init():初始化線程屬性變量。運行后前计,
pthread_attr_t
結(jié)構(gòu)所包含的內(nèi)容是操作系統(tǒng)支持的線程的所有屬性的默認值胞谭。pthread_attr_setdetachstate():設置線程屬性變量的
detachstate
屬性(決定線程在終止時是否可以被joinable
)pthread_attr_getdetachstate():獲取
detachstate
的屬性pthread_attr_setscope():設置線程屬性變量的
__scope
屬性pthread_attr_setschedparam():設置線程屬性變量的
schedparam
屬性,即調(diào)用的優(yōu)先級男杈。pthread_attr_getschedparam():獲取線程屬性變量的
schedparam
屬性丈屹,即調(diào)用的優(yōu)先級。pthread_attr_destroy():刪除線程的屬性,用無效值覆蓋
mutex函數(shù):
pthread_mutex_init():初始化互斥鎖
pthread_mutex_destroy():刪除互斥鎖
pthread_mutex_lock():占有互斥鎖(阻塞操作)
pthread_mutex_trylock():試圖占有互斥鎖(不阻塞操作)旺垒。即彩库,當互斥鎖空閑時,將占有該鎖先蒋;否則骇钦,立即返回。
pthread_mutex_unlock(): 釋放互斥鎖
pthread_mutexattr_(): 互斥鎖屬性相關的函數(shù)
條件變量函數(shù)
pthread_cond_init():初始化條件變量
pthread_cond_destroy():銷毀條件變量
pthread_cond_signal(): 發(fā)送一個[信號]給正在當前條件變量的線程隊列中處于阻塞等待狀態(tài)的線程竞漾,使其脫離阻塞狀態(tài)眯搭,喚醒后繼續(xù)執(zhí)行。如果沒有線程處在阻塞等待狀態(tài)业岁,
pthread_cond_signal
也會成功返回鳞仙。一般只給一個阻塞狀態(tài)的線程發(fā)信號。假如有多個線程正在阻塞等待當前條件變量叨襟,則根據(jù)各等待線程優(yōu)先級
的高低確定哪個線程接收到信號開始繼續(xù)執(zhí)行繁扎。如果各線程優(yōu)先級相同,則根據(jù)等待時間
的長短來確定哪個線程獲得信號糊闽。但pthread_cond_signal
在多處理器上可能同時喚醒多個線程梳玫,當只能讓一個被喚醒的線程處理某個任務時,其它被喚醒的線程就需要繼續(xù)wait
右犹。POSIX規(guī)范要求pthread_cond_signal
至少喚醒一個pthread_cond_wait
上的線程提澎,有些實現(xiàn)為了簡便,在單處理器上也會喚醒多個線程念链。所以最好對pthread_cond_wait()
使用while
循環(huán)對條件變量是否滿足做條件判斷盼忌。pthread_cond_wait(): 等待條件變量的特殊條件發(fā)生;
pthread_cond_wait()
必須與一個pthread_mutex
配套使用掂墓。該函數(shù)調(diào)用實際上依次做了3件事:對當前pthread_mutex解鎖
谦纱、把當前線程掛起到當前條件變量的線程隊列、被其它線程的信號喚醒后對當前pthread_mutex
申請加鎖
君编。如果線程收到一個信號被喚醒跨嘉,將被配套的互斥鎖重新鎖住,pthread_cond_wait()
函數(shù)將不返回直到線程獲得配套的互斥鎖吃嘿。需要注意的是祠乃,一個條件變量不應該與多個互斥鎖配套使用
。pthread_cond_broadcast(): 某些應用兑燥,如線程池亮瓷,
pthread_cond_broadcast
喚醒全部線程,但我們通常只需要一部分線程去做執(zhí)行任務降瞳,所以其它的線程需要繼續(xù)wait
.pthread_condattr_(): 條件變量屬性相關的函數(shù)
線程私有存儲(Thread-local storage):
pthread_key_create(): 分配用于標識進程中線程特定數(shù)據(jù)的
pthread_key_t
類型的鍵
pthread_key_delete(): 銷毀現(xiàn)有線程特定數(shù)據(jù)
鍵
pthread_setspecific(): 為指定線程的特定數(shù)據(jù)
鍵
設置綁定的值
pthread_getspecific(): 獲取調(diào)用線程的
鍵
綁定值
嘱支,并將該綁定存儲
在 value 指向的位置中
同步屏障函數(shù)
pthread_barrier_init(): 同步屏障初始化
pthread_barrier_wait():
pthread_barrier_destory():
其它多線程同步函數(shù):
- pthread_rwlock_*(): 讀寫鎖
工具函數(shù):
pthread_equal(): 對兩個線程的線程標識號進行比較
pthread_detach(): 分離線程
pthread_self(): 查詢線程自身線程標識號
pthread_once(): 某些需要僅執(zhí)行一次的函數(shù)。其中第一個參數(shù)為
pthread_once_t
類型,是內(nèi)部實現(xiàn)的互斥鎖除师,保證在程序全局僅執(zhí)行一次赢织。
信號量函數(shù),
包含在semaphore.h中:
sem_open:創(chuàng)建或者打開已有的命名信號量馍盟。可分為
二值信號量
與計數(shù)信號量
茧吊。命名信號量可以在進程間共享使用贞岭。sem_close:關閉一個信號燈,但沒有將它從系統(tǒng)中刪除搓侄。命名信號燈是隨內(nèi)核持續(xù)的瞄桨,即使當前沒有進程打開著某個信號燈,它的值仍然保持讶踪。
sem_unlink:從系統(tǒng)中刪除信號燈芯侥。
sem_getvalue:返回所指定信號燈的當前值乳讥。如果該信號燈當前已上鎖柱查,那么返回值或為0唉工,或為某個負數(shù)汹忠,其
絕對值
就是等待該信號燈解鎖的線程數(shù)
淋硝。sem_wait:申請共享資源,所指定信號燈的值如果
大于0
宽菜,那就將它減1
并立即返回谣膳,就可以使用申請來的共享資源了。如果該值等于0
铅乡,調(diào)用線程就被進入睡眠
狀態(tài)继谚,直到該值變?yōu)榇笥?,這時再將它減1隆判,函數(shù)隨后返回犬庇。sem_wait
操作必須是原子操作
。sem_trywait:申請共享資源侨嘀,當所指定信號燈的值
已經(jīng)是0時
臭挽,后者并不
將調(diào)用線程投入睡眠
。相反咬腕,它返回一個EAGAIN錯誤欢峰。sem_post:釋放共享資源。與
sem_wait
恰相反。sem_init:初始化非命名(內(nèi)存)信號量
sem_destroy:銷毀非命名信號量
共享內(nèi)存函數(shù)
包含在sys/mman.h中纽帖,鏈接時使用rt庫:
mmap:把一個文件或一個POSIX共享內(nèi)存區(qū)對象
映射
到調(diào)用進程的地址空間
宠漩。使用該函數(shù)的目的: 1.使用普通文件以提供內(nèi)存映射I/O 2.使用特殊文件以提供匿名內(nèi)存映射。 3.使用shm_open
以提供無親緣關系進程間的POSIX共享內(nèi)存區(qū)懊直。munmap: 刪除一個映射關系
msync:
文件
與內(nèi)存
同步函數(shù)shm_open:創(chuàng)建或打開共享內(nèi)存區(qū)
shm_unlink:刪除一個共享內(nèi)存區(qū)對象的名字扒吁,刪除一個名字僅僅防止后續(xù)的
open
,msq_open
或sem_open
調(diào)用取得成功盼产。ftruncate:調(diào)整文件或共享內(nèi)存區(qū)大小
fstat:來獲取有關該對象的信息
文章參考:https://blog.csdn.net/jiajun2001/article/details/12624923
拓展閱讀:
http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html
https://randu.org/tutorials/threads/