本文節(jié)選自成長手冊
-
文章推薦和參考
多線程編程被普遍認(rèn)為復(fù)雜肢执,主要是因?yàn)槎嗑€程給程序引入了一定的不可預(yù)知性成畦,要控制這些不可預(yù)知性迫靖,就需要使用各種鎖各種同步機(jī)制自阱,不同的情況就應(yīng)該使用不同的鎖不同的機(jī)制瘩缆。
什么事情一旦放到多線程環(huán)境背桐,要考慮的問題立刻就上升了好幾個(gè)量級(jí)优烧。多線程編程帶來的好處不可勝數(shù),然而工程師只要一不小心链峭,就很容易讓你的程序失去控制畦娄,所以你得用各種鎖各種機(jī)制管住它。
要解決好這些問題弊仪,工程師們就要充分了解這些鎖機(jī)制纷责,分析不同的場景,選擇合適的解決方案撼短。
- 臨界區(qū):指的是一塊對(duì)公共資源進(jìn)行訪問的代碼,并非一種機(jī)制或是算法挺勿。
- 自旋鎖:是用于多線程同步的一種鎖曲横,線程反復(fù)檢查鎖變量是否可用。由于線程在這一過程中保持執(zhí)行不瓶,因此是一種忙等待禾嫉。一旦獲取了自旋鎖,線程會(huì)一直保持該鎖蚊丐,直至顯式釋放自旋鎖熙参。 自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對(duì)于線程只會(huì)阻塞很短時(shí)間的場合是有效的麦备。
-
互斥鎖(
Mutex
):是一種用于多線程編程中孽椰,防止兩條線程同時(shí)對(duì)同一公共資源(比如全局變量)進(jìn)行讀寫的機(jī)制。該目的通過將代碼切片成一個(gè)一個(gè)的臨界區(qū)而達(dá)成凛篙。 - 讀寫鎖:是計(jì)算機(jī)程序的并發(fā)控制的一種同步機(jī)制黍匾,也稱“共享-互斥鎖”、多讀者-單寫者鎖) 用于解決多線程對(duì)公共資源讀寫問題呛梆。讀操作可并發(fā)重入锐涯,寫操作是互斥的。 讀寫鎖通常用互斥鎖填物、條件變量纹腌、信號(hào)量實(shí)現(xiàn)霎终。
-
信號(hào)量(
semaphore
):是一種更高級(jí)的同步機(jī)制,互斥鎖可以說是semaphore在僅取值0/1時(shí)的特例升薯。信號(hào)量可以有更多的取值空間夏跷,用來實(shí)現(xiàn)更加復(fù)雜的同步,而不單單是線程間互斥猖毫。 - 條件鎖:就是條件變量吃度,當(dāng)進(jìn)程的某些資源要求不滿足時(shí)就進(jìn)入休眠,也就是鎖住了责语。當(dāng)資源被分配到了炮障,條件鎖打開,進(jìn)程繼續(xù)運(yùn)行坤候。
- 死鎖:指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中胁赢,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用白筹,它們都將無法推進(jìn)下去智末,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程。
-
輪詢(Polling):一種
CPU
決策如何提供周邊設(shè)備服務(wù)的方式徒河,又稱“程控輸出入”系馆。輪詢法的概念是,由CPU
定時(shí)發(fā)出詢問顽照,依序詢問每一個(gè)周邊設(shè)備是否需要其服務(wù)由蘑,有即給予服務(wù),服務(wù)結(jié)束后再問下一個(gè)周邊代兵,接著不斷周而復(fù)始尼酿。
1400498-2bfca138992bb7d8.png
//10000000
OSSpinLock: 112.38 ms
dispatch_semaphore: 160.37 ms
os_unfair_lock: 208.87 ms
pthread_mutex: 302.07 ms
NSCondition: 320.11 ms
NSLock: 331.80 ms
pthread_rwlock: 360.81 ms
pthread_mutex(recursive): 512.17 ms
NSRecursiveLock: 667.55 ms
NSConditionLock: 999.91 ms
@synchronized: 1654.92 ms
//1000
OSSpinLock: 0.02 ms
dispatch_semaphore: 0.03 ms
os_unfair_lock: 0.04 ms
pthread_mutex: 0.06 ms
NSLock: 0.06 ms
pthread_rwlock: 0.07 ms
NSCondition: 0.07 ms
pthread_mutex(recursive): 0.09 ms
NSRecursiveLock: 0.12 ms
NSConditionLock: 0.18 ms
@synchronized: 0.33 ms
- 互斥鎖
NSLock
pthread_mutex
pthread_mutex(recursive)
遞歸鎖@synchronized
- 自旋鎖
OSSpinLock
os_unfair_lock
- 讀寫鎖
pthread_rwlock
- 遞歸鎖
NSRecursiveLock
-
pthread_mutex(recursive)
(見上)
- 條件鎖
NSCondition
NSConditionLock
- 信號(hào)量
dispatch_semaphore
互斥鎖
1、NSLock
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
-
NSLock
遵循NSLocking
協(xié)議植影,lock
方法是加鎖裳擎,unlock
是解鎖,tryLock
是嘗試加鎖思币,如果失敗的話返回NO
鹿响,lockBeforeDate:
是在指定Date
之前嘗試加鎖,如果在指定時(shí)間之前都不能加鎖谷饿,則返回NO
抢野。
// 主線程中
NSLock *lock = [[NSLock alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程1");
sleep(2);
[lock unlock];
sleep(1);
NSLog(@"線程1解鎖成功");
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
NSLog(@"線程2");
[lock unlock];
});
// 打印:
// 線程1
// 線程2
// 線程1解鎖成功
- 如果是三個(gè)線程各墨,那么一個(gè)線程在加鎖的時(shí)候指孤,其余請求鎖的線程將形成一個(gè)等待隊(duì)列,按先進(jìn)先出原則
- 注意:要求
NSLock
的lock
和unlock
需要在同一個(gè)Thread
下面
2、pthread_mutex
-
pthread_mutex
是C
語言下多線程加互斥鎖的方式 - 被這個(gè)鎖保護(hù)的臨界區(qū)就只允許一個(gè)線程進(jìn)入恃轩,其它線程如果沒有獲得鎖權(quán)限结洼,那就只能在外面等著。
// 用于靜態(tài)的mutex的初始化叉跛,采用默認(rèn)的attr松忍。比如: static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
#define PTHREAD_MUTEX_INITIALIZER {_PTHREAD_MUTEX_SIG_init, {0}}
// 動(dòng)態(tài)的初始化一個(gè)鎖,__restrict 為互斥鎖的類型筷厘,傳 NULL 為默認(rèn)類型鸣峭,一共有 4 類型。
int pthread_mutex_init(pthread_mutex_t * __restrict, const pthread_mutexattr_t * __restrict);
// 請求鎖酥艳,如果當(dāng)前mutex已經(jīng)被鎖摊溶,那么這個(gè)線程就會(huì)卡在這兒,直到mutex被釋放
int pthread_mutex_lock(pthread_mutex_t *);
// 嘗試請求鎖充石,如果當(dāng)前mutex已經(jīng)被鎖或者不可用莫换,這個(gè)函數(shù)就直接return了,不會(huì)把線程卡住
int pthread_mutex_trylock(pthread_mutex_t *);
// 解鎖
int pthread_mutex_unlock(pthread_mutex_t *);
// 把mutex鎖干掉骤铃,并且釋放所有它所占有的資源
int pthread_mutex_destroy(pthread_mutex_t *);
int pthread_mutex_setprioceiling(pthread_mutex_t * __restrict, int,
int * __restrict);
int pthread_mutex_getprioceiling(const pthread_mutex_t * __restrict,
int * __restrict);
- 鎖類型
PTHREAD_MUTEX_NORMAL 缺省類型拉岁,也就是普通鎖。當(dāng)一個(gè)線程加鎖以后惰爬,其余請求鎖的線程將形成一個(gè)等待隊(duì)列喊暖,并在解鎖后先進(jìn)先出原則獲得鎖。
PTHREAD_MUTEX_ERRORCHECK 檢錯(cuò)鎖撕瞧,如果同一個(gè)線程請求同一個(gè)鎖陵叽,則返回 EDEADLK,否則與普通鎖類型動(dòng)作相同风范。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)嵌套情況下的死鎖。
PTHREAD_MUTEX_RECURSIVE 遞歸鎖沪么,允許同一個(gè)線程對(duì)同一個(gè)鎖成功獲得多次硼婿,并通過多次 unlock 解鎖。
PTHREAD_MUTEX_DEFAULT 適應(yīng)鎖禽车,動(dòng)作最簡單的鎖類型寇漫,僅等待解鎖后重新競爭,沒有等待隊(duì)列殉摔。
-
mutex
的初始化分兩種州胳,一種是用宏(PTHREAD_MUTEX_INITIALIZER
),一種是用函數(shù)(pthread_mutex_init
)逸月。- 如果沒有特殊的配置要求的話栓撞,使用宏比較好,因?yàn)樗容^快。只有真的需要配置的時(shí)候瓤湘,才需要用函數(shù)瓢颅。
- 也就是說,凡是
pthread_mutex_init(&mutex, NULL)
的地方都可以使用PTHREAD_MUTEX_INITIALIZER
弛说,因?yàn)樵?code>pthread_mutex_init這個(gè)函數(shù)里的實(shí)現(xiàn)其實(shí)也是用了PTHREAD_MUTEX_INITIALIZER
:
///////////////////// pthread_src/include/pthread/pthread.h
#define PTHREAD_MUTEX_INITIALIZER __PTHREAD_MUTEX_INITIALIZER
///////////////////// pthread_src/sysdeps/generic/bits/mutex.h
// mutex鎖本質(zhì)上是一個(gè)spin lock挽懦,空轉(zhuǎn)鎖
# define __PTHREAD_MUTEX_INITIALIZER \
{ __PTHREAD_SPIN_LOCK_INITIALIZER, __PTHREAD_SPIN_LOCK_INITIALIZER, 0, 0, 0, 0, 0, 0 }
///////////////////// pthread_src/sysdeps/generic/pt-mutex-init.c
// 你看,這里其實(shí)用的也是宏木人。就這一句是初始化信柿,下面都是在設(shè)置屬性。
int
_pthread_mutex_init (pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr)
{
*mutex = (pthread_mutex_t) __PTHREAD_MUTEX_INITIALIZER;
if (! attr
|| memcmp (attr, &__pthread_default_mutexattr, sizeof (*attr) == 0))
/* The default attributes. */
return 0;
if (! mutex->attr
|| mutex->attr == __PTHREAD_ERRORCHECK_MUTEXATTR
|| mutex->attr == __PTHREAD_RECURSIVE_MUTEXATTR)
//pthread_mutex_destroy釋放的就是這里的資源
mutex->attr = malloc (sizeof *attr);
if (! mutex->attr) return ENOMEM;
*mutex->attr = *attr;
return 0;
}
業(yè)界有另一種說法是:早年的
POSIX
只支持在static
變量上使用PTHREAD_MUTEX_INITIALIZER
醒第,所以PTHREAD_MUTEX_INITIALIZER
盡量不要到處都用渔嚷,所以使用的時(shí)候你得搞清楚你的pthread
的實(shí)現(xiàn)版本是不是比較老的。使用
#import <pthread.h>
static pthread_mutex_t theLock;
- (void)example5 {
pthread_mutex_init(&theLock, NULL);
pthread_t thread;
pthread_create(&thread, NULL, threadMethord1, NULL);
pthread_t thread2;
pthread_create(&thread2, NULL, threadMethord2, NULL);
}
void *threadMethord1() {
pthread_mutex_lock(&theLock);
printf("線程1\n");
sleep(2);
pthread_mutex_unlock(&theLock);
printf("線程1解鎖成功\n");
return 0;
}
void *threadMethord2() {
sleep(1);
pthread_mutex_lock(&theLock);
printf("線程2\n");
pthread_mutex_unlock(&theLock);
return 0;
}
// 打犹约ァ:
// 線程1
// 線程1解鎖成功
// 線程2
-
mutex
鎖不是萬能靈藥- 基本上所有的問題都可以用互斥的方案去解決圃伶,大不了就是慢點(diǎn)兒,但不要不管什么情況都用互斥蒲列,都能采用這種方案不代表都適合采用這種方案窒朋。
- 而且這里所說的慢不是說
mutex
的實(shí)現(xiàn)方案比較慢,而是互斥方案影響的面比較大蝗岖,本來不需要通過互斥就能讓線程進(jìn)入臨界區(qū)侥猩,但用了互斥方案之后,就使這樣的線程不得不等待互斥鎖的釋放抵赢,所以就慢了欺劳。 - 甚至有些場合用互斥就很蛋疼,比如多資源分配铅鲤,線程步調(diào)通知等划提。 如果是讀多寫少的場合,就比較適合讀寫鎖邢享,如果臨界區(qū)比較短鹏往,就適合空轉(zhuǎn)鎖,后面有介紹
-
預(yù)防死鎖
- 1骇塘、如果要進(jìn)入一段臨界區(qū)需要多個(gè)
mutex
鎖伊履,那么就很容易導(dǎo)致死鎖,單個(gè)mutex
鎖是不會(huì)引發(fā)死鎖的款违。- 要解決這個(gè)問題也很簡單唐瀑,只要申請鎖的時(shí)候按照固定順序,或者及時(shí)釋放不需要的
mutex
鎖就可以插爹,尤其是全局mutex
鎖的時(shí)候哄辣,更需要遵守一個(gè)約定请梢。 - 我的
mutex
鎖的命名規(guī)則就是:-
作用
_mutex_
序號(hào),比如LinkListMutex_mutex_1
,OperationQueue_mutex_2
柔滔,后面的序號(hào)在每次有新鎖的時(shí)候溢陪,就都加一個(gè)1
。如果有哪個(gè)臨界區(qū)進(jìn)入的時(shí)候需要獲得多個(gè)mutex
鎖的睛廊,我就按照序號(hào)的順序去進(jìn)行加鎖操作形真,這樣就能夠保證不會(huì)出現(xiàn)死鎖了。 - 如果是屬于某個(gè)
struct
內(nèi)部的mutex
鎖超全,也一樣咆霜,只不過序號(hào)可以不必跟全局鎖掛鉤,也可以從1
開始數(shù)嘶朱。
-
作用
- 要解決這個(gè)問題也很簡單唐瀑,只要申請鎖的時(shí)候按照固定順序,或者及時(shí)釋放不需要的
- 2蛾坯、還有另一種方案也非常有效,就是用
pthread_mutex_trylock
函數(shù)來申請加鎖疏遏,這個(gè)函數(shù)在mutex
鎖不可用時(shí)脉课,不像pthread_mutex_lock
那樣會(huì)等待。pthread_mutex_trylock
在申請加鎖失敗時(shí)立刻就會(huì)返回錯(cuò)誤:EBUSY
(鎖尚未解除)或者EINVAL
(鎖變量不可用)财异。- 一旦在
trylock
的時(shí)候有錯(cuò)誤返回倘零,那就把前面已經(jīng)拿到的鎖全部釋放,然后過一段時(shí)間再來一遍戳寸。 - 當(dāng)然也可以使用
pthread_mutex_timedlock
這個(gè)函數(shù)來申請加鎖呈驶,這個(gè)函數(shù)跟pthread_mutex_trylock
類似,不同的是疫鹊,你可以傳入一個(gè)時(shí)間參數(shù)袖瞻,在申請加鎖失敗之后會(huì)阻塞一段時(shí)間等解鎖,超時(shí)之后才返回錯(cuò)誤拆吆。
- 一旦在
- 這兩種方案我更多會(huì)使用第一種聋迎,原因如下:
- 一般情況下進(jìn)入臨界區(qū)需要加的鎖數(shù)量不會(huì)太多,第一種方案能夠
hold
住枣耀。如果多于2
個(gè)霉晕,你就要考慮一下是否有些鎖是可以合并的了。第一種方案適合鎖比較少的情況奕枢,因?yàn)檫@不會(huì)導(dǎo)致非常大的阻塞延時(shí)娄昆。但是當(dāng)你要加的鎖非常多佩微,A缝彬、B、C哺眯、D谷浅、E
,你加到D
的時(shí)候阻塞了,然而其他線程可能只需要A一疯、B
就可以運(yùn)行撼玄,就也會(huì)因?yàn)?code>A、B已經(jīng)被鎖住而阻塞墩邀,這時(shí)候才會(huì)采用第二種方案掌猛。如果要加的鎖本身就不多,只有A眉睹、B
兩個(gè)荔茬,那么阻塞一下也還可以。 - 第二種方案在面臨阻塞的時(shí)候竹海,要操作的事情太多慕蔚。當(dāng)你把所有的鎖都釋放以后,你的當(dāng)前線程的處理策略就會(huì)導(dǎo)致你的代碼復(fù)雜度上升:當(dāng)前線程總不能就此退出吧斋配,你得找個(gè)地方把它放起來孔飒,讓它去等待一段時(shí)間之后再去申請鎖,如果有多個(gè)線程出現(xiàn)了這樣的情況艰争,你就需要一個(gè)線程池來存放這些等待解鎖的線程坏瞄。如果臨界區(qū)是嵌套的,你在把這個(gè)線程掛起的時(shí)候园细,最好還要把外面的鎖也釋放掉惦积,要不然也會(huì)容易導(dǎo)致死鎖,這就需要你在一個(gè)地方記錄當(dāng)前線程使用鎖的情況猛频。這里要做的事情太多狮崩,復(fù)雜度比較大,容易出錯(cuò)鹿寻。
- 一般情況下進(jìn)入臨界區(qū)需要加的鎖數(shù)量不會(huì)太多,第一種方案能夠
- 所以總而言之睦柴,設(shè)計(jì)的時(shí)候盡量減少同一臨界區(qū)所需要
mutex
鎖的數(shù)量,然后采用第一種方案毡熏。如果確實(shí)有需求導(dǎo)致那么多mutex
鎖坦敌,那么就只能采用第二種方案了,然后老老實(shí)實(shí)寫好周邊代碼痢法。 - 但是到了
semaphore
情況下的死鎖處理方案時(shí)狱窘,上面兩種方案就都不頂用了,后面我會(huì)說财搁。另外蘸炸,還有一種死鎖是自己把自己鎖死了,這個(gè)我在后面也會(huì)說尖奔。
- 1骇塘、如果要進(jìn)入一段臨界區(qū)需要多個(gè)
pthread_mutex(recursive)遞歸鎖
-
pthread_mutex
鎖也支持遞歸搭儒,只需要設(shè)置PTHREAD_MUTEX_RECURSIVE
即可 - 這是
pthread_mutex
為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖穷当。作用和NSRecursiveLock
遞歸鎖類似。 - 通過
pthread_mutexattr_t
來設(shè)置鎖的類型淹禾,如下面代碼就設(shè)置鎖為遞歸鎖馁菜。
- (void)example5 {
// 定義mutex
pthread_mutex_init(&theLock, NULL);
// 定義mutexattr
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&theLock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_t thread;
pthread_create(&thread, NULL, threadMethord, 5);
}
void *threadMethord(int value) {
pthread_mutex_lock(&theLock);
if (value > 0) {
printf("Value:%i\n", value);
sleep(1);
threadMethord(value - 1);
}
pthread_mutex_unlock(&theLock);
return 0;
}
// Value:5
// Value:4
// Value:3
// Value:2
// Value:1
3、@synchronized
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
sleep(2);
NSLog(@"線程1");
}
sleep(1);
NSLog(@"線程1解鎖成功");
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self) {
NSLog(@"線程2");
}
});
// 線程1
// 線程2
// 線程1解鎖成功
-
@synchronized(object)
指令使用的object
為該鎖的唯一標(biāo)識(shí)铃岔,只有當(dāng)標(biāo)識(shí)相同時(shí)汪疮,才滿足互斥,所以如果線程2
中的@synchronized(self)
改為@synchronized(self.view)
毁习,則線程2
就不會(huì)被阻塞 -
@synchronized
指令實(shí)現(xiàn)鎖的優(yōu)點(diǎn)就是我們不需要在代碼中顯式的創(chuàng)建鎖對(duì)象铲咨,便可以實(shí)現(xiàn)鎖的機(jī)制,但作為一種預(yù)防措施蜓洪,@synchronized
塊會(huì)隱式的添加一個(gè)異常處理例程來保護(hù)代碼纤勒,該處理例程會(huì)在異常拋出的時(shí)候自動(dòng)的釋放互斥鎖。 - 如果在
@sychronized(object){}
內(nèi)部object
被釋放或被設(shè)為nil
隆檀,從測試的結(jié)果來看摇天,的確沒有問題,但如果object
一開始就是nil
恐仑,則失去了鎖的功能泉坐。但@synchronized([NSNull null])
是完全可以的。
自旋鎖
1裳仆、OSSpinLock
-
OSSpinLock
是一種自旋鎖腕让,也只有加鎖,解鎖歧斟,嘗試加鎖三個(gè)方法纯丸。 - 和
NSLock
不同的是NSLock
請求加鎖失敗的話,會(huì)先輪詢静袖,但一秒過后便會(huì)使線程進(jìn)入waiting
狀態(tài)觉鼻,等待喚醒。 - 而
OSSpinLock
會(huì)一直輪詢队橙,等待時(shí)會(huì)消耗大量CPU
資源坠陈,不適用于較長時(shí)間的任務(wù)。 -
10.0
棄用捐康,使用os_unfair_lock
typedef int32_t OSSpinLock;
//嘗試加鎖
bool OSSpinLockTry( volatile OSSpinLock *__lock );
//加鎖
void OSSpinLockLock( volatile OSSpinLock *__lock );
//解鎖
void OSSpinLockUnlock( volatile OSSpinLock *__lock );
- 使用
#import <libkern/OSSpinLockDeprecated.h>
__block OSSpinLock theLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
NSLog(@"需要線程同步的操作1 開始");
sleep(3);
NSLog(@"需要線程同步的操作1 結(jié)束");
OSSpinLockUnlock(&theLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OSSpinLockLock(&theLock);
sleep(1);
NSLog(@"需要線程同步的操作2");
OSSpinLockUnlock(&theLock);
});
// 需要線程同步的操作1 開始
// 需要線程同步的操作1 結(jié)束
// 需要線程同步的操作2
os_unfair_lock
-
os_unfair_lock
是蘋果官方推薦的替換OSSpinLock
的方案仇矾,但是它在iOS10.0
以上的系統(tǒng)才可以調(diào)用。
// 簡單使用
os_unfair_lock_t unfairLock;
unfairLock = &(OS_UNFAIR_LOCK_INIT);
os_unfair_lock_lock(unfairLock);
os_unfair_lock_unlock(unfairLock);
讀寫鎖
1解总、pthread_rwlock
- 前面互斥鎖
mutex
有個(gè)缺點(diǎn)贮匕,就是只要鎖住了,不管其他線程要干什么倾鲫,都不允許進(jìn)入臨界區(qū)粗合。- 設(shè)想這樣一種情況:臨界區(qū)
foo
變量在被bar1
線程讀著,加了個(gè)mutex
鎖乌昔,bar2
線程如果也要讀foo
變量隙疚,因?yàn)楸?code>bar1加了個(gè)互斥鎖,那就不能讀了磕道。 - 但事實(shí)情況是供屉,讀取數(shù)據(jù)不影響數(shù)據(jù)內(nèi)容本身,所以即便被
1
個(gè)線程讀著溺蕉,另外一個(gè)線程也應(yīng)該允許他去讀伶丐。除非另外一個(gè)線程是寫操作,為了避免數(shù)據(jù)不一致的問題疯特,寫線程就需要等讀線程都結(jié)束了再寫哗魂。
- 設(shè)想這樣一種情況:臨界區(qū)
- 因此誕生了讀寫鎖,有的地方也叫共享鎖漓雅。
- 讀寫鎖的特性是這樣的
- 當(dāng)一個(gè)線程加了讀鎖訪問臨界區(qū)录别,另外一個(gè)線程也想訪問臨界區(qū)讀取數(shù)據(jù)的時(shí)候,也可以加一個(gè)讀鎖邻吞,這樣另外一個(gè)線程就能夠成功進(jìn)入臨界區(qū)進(jìn)行讀操作了组题。此時(shí)讀鎖線程有兩個(gè)。
- 當(dāng)?shù)谌齻€(gè)線程需要進(jìn)行寫操作時(shí)抱冷,它需要加一個(gè)寫鎖崔列,這個(gè)寫鎖只有在讀鎖的擁有者為
0
時(shí)才有效。也就是等前兩個(gè)讀線程都釋放讀鎖之后旺遮,第三個(gè)線程就能進(jìn)去寫了赵讯。- 總結(jié)一下就是,讀寫鎖里耿眉,讀鎖能允許多個(gè)線程同時(shí)去讀瘦癌,但是寫鎖在同一時(shí)刻只允許一個(gè)線程去寫。
- 這樣更精細(xì)的控制跷敬,就能減少
mutex
導(dǎo)致的阻塞延遲時(shí)間讯私。雖然用mutex
也能起作用,但這種場合西傀,明顯讀寫鎖更好斤寇!
PTHREAD_RWLOCK_INITIALIZER
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//加讀鎖
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//加寫鎖
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//解鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
// 這個(gè)函數(shù)在Linux和Mac的man文檔里都沒有,新版的pthread.h里面也沒有拥褂,舊版的能找到
int pthread_rwlock_timedrdlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime);
// 同上
int pthread_rwlock_timedwrlock_np(pthread_rwlock_t *rwlock, const struct timespec *deltatime);
注意的地方
- 命名
- 跟上面提到的寫
muetx
互斥鎖的約定一樣娘锁,操作,類別饺鹃,序號(hào)最好都要有莫秆。比如OperationQueue_rwlock_1
间雀。
- 跟上面提到的寫
- 認(rèn)真區(qū)分使用場合
- 由于讀寫鎖的性質(zhì),在默認(rèn)情況下是很容易出現(xiàn)寫線程饑餓的镊屎。因?yàn)樗仨氁鹊剿凶x鎖都釋放之后惹挟,才能成功申請寫鎖。不過不同系統(tǒng)的實(shí)現(xiàn)版本對(duì)寫線程的優(yōu)先級(jí)實(shí)現(xiàn)不同缝驳。
Solaris
下面就是寫線程優(yōu)先连锯,其他系統(tǒng)默認(rèn)讀線程優(yōu)先。 - 比如在寫線程阻塞的時(shí)候用狱,有很多讀線程是可以一個(gè)接一個(gè)地在那兒插隊(duì)的(在默認(rèn)情況下运怖,只要有讀鎖在,寫鎖就無法申請夏伊,然而讀鎖可以一直申請成功摇展,就導(dǎo)致所謂的插隊(duì)現(xiàn)象),那么寫線程就不知道什么時(shí)候才能申請成功寫鎖了溺忧,然后它就餓死了吗购。
- 為了控制寫線程饑餓,必須要在創(chuàng)建讀寫鎖的時(shí)候設(shè)置
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE
砸狞,不要用PTHREAD_RWLOCK_PREFER_WRITER_NP
捻勉,這個(gè)似乎沒什么用,感覺應(yīng)該是個(gè)bug
- 由于讀寫鎖的性質(zhì),在默認(rèn)情況下是很容易出現(xiàn)寫線程饑餓的镊屎。因?yàn)樗仨氁鹊剿凶x鎖都釋放之后惹挟,才能成功申請寫鎖。不過不同系統(tǒng)的實(shí)現(xiàn)版本對(duì)寫線程的優(yōu)先級(jí)實(shí)現(xiàn)不同缝驳。
////////////////////////////// /usr/include/pthread.h
/* Read-write lock types. */
#if defined __USE_UNIX98 || defined __USE_XOPEN2K
enum
{
PTHREAD_RWLOCK_PREFER_READER_NP,
PTHREAD_RWLOCK_PREFER_WRITER_NP, // 媽蛋刀森,沒用踱启,一樣reader優(yōu)先
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
PTHREAD_RWLOCK_DEFAULT_NP = PTHREAD_RWLOCK_PREFER_READER_NP
};
- 總的來說,這樣的鎖建立之后一定要設(shè)置優(yōu)先級(jí)研底,不然就容易出現(xiàn)寫線程饑餓埠偿。而且讀寫鎖適合讀多寫少的情況,如果讀榜晦、寫一樣多冠蒋,那這時(shí)候還是用
mutex
互斥鎖比較合理。
遞歸鎖
- 遞歸鎖有一個(gè)特點(diǎn)乾胶,就是同一個(gè)線程可以加鎖
N
次而不會(huì)引發(fā)死鎖抖剿。
1、NSRecursiveLock
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
-
NSRecursiveLock
是遞歸鎖识窿,他和NSLock
的區(qū)別在于斩郎,NSRecursiveLock
可以在一個(gè)線程中重復(fù)加鎖(反正單線程內(nèi)任務(wù)是按順序執(zhí)行的,不會(huì)出現(xiàn)資源競爭問題)喻频,NSRecursiveLock
會(huì)記錄上鎖和解鎖的次數(shù)缩宜,當(dāng)二者平衡的時(shí)候,才會(huì)釋放鎖,其它線程才可以上鎖成功锻煌。
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveBlock)(int);
RecursiveBlock = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value:%d", value);
RecursiveBlock(value - 1);
}
[lock unlock];
};
RecursiveBlock(2);
});
// value:2
// value:1
- 如上面的示例妓布,如果用
NSLock
的話,lock
先鎖上了宋梧,但未執(zhí)行解鎖的時(shí)候匣沼,就會(huì)進(jìn)入遞歸的下一層,而再次請求上鎖乃秀,阻塞了該線程,線程被阻塞了圆兵,自然后面的解鎖代碼不會(huì)執(zhí)行跺讯,而形成了死鎖。而NSRecursiveLock
遞歸鎖就是為了解決這個(gè)問題殉农。主要是用在循環(huán)或遞歸操作中刀脏。 - 這段代碼是一個(gè)典型的死鎖情況。在我們的線程中超凳,
RecursiveMethod
是遞歸調(diào)用的愈污。所以每次進(jìn)入這個(gè)block
時(shí),都會(huì)去加一次鎖轮傍,而從第二次開始暂雹,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除创夜,這樣就導(dǎo)致了死鎖杭跪,線程被阻塞住了。
2驰吓、pthread_mutex(recursive)
見上文
條件鎖
1涧尿、NSCondition
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
- 一種最基本的條件鎖。手動(dòng)控制線程
wait
和signal
檬贰。 - 遵循
NSLocking
協(xié)議姑廉,使用的時(shí)候同樣是lock
,unlock
加解鎖,wait
是傻等翁涤,waitUntilDate:
方法是等一會(huì)桥言,都會(huì)阻塞掉線程,signal
是喚起一個(gè)在等待的線程葵礼,broadcast
是廣播全部喚起限书。 -
NSCondition
的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器,鎖上之后其它線程也能上鎖章咧,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線程倦西,即線程是否要進(jìn)入waiting
狀態(tài),經(jīng)測試赁严,NSCondition
并不會(huì)像上文的那些鎖一樣扰柠,先輪詢粉铐,而是直接進(jìn)入waiting
狀態(tài),當(dāng)其它線程中的該鎖執(zhí)行signal
或者broadcast
方法時(shí)卤档,線程被喚醒蝙泼,繼續(xù)運(yùn)行之后的方法。
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
while (!array.count) {
[lock wait];
}
[array removeAllObjects];
NSLog(@"array removeAllObjects");
[lock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保證讓線程2的代碼后執(zhí)行
[lock lock];
[array addObject:@1];
NSLog(@"array addObject:@1");
[lock signal];
[lock unlock];
});
// array addObject:@1
// array removeAllObjects
- 其中
signal
和broadcast
方法的區(qū)別在于劝枣,signal
只是一個(gè)信號(hào)量汤踏,只能喚醒一個(gè)等待的線程,想喚醒多個(gè)就得多次調(diào)用舔腾,而broadcast
可以喚醒所有在等待的線程溪胶。如果沒有等待的線程,這兩個(gè)方法都沒有作用稳诚。
2哗脖、NSConditionLock
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
-
NSConditionLock
和NSLock
類似,都遵循NSLocking
協(xié)議扳还,方法都類似才避,只是多了一個(gè)condition
屬性,以及每個(gè)操作都多了一個(gè)關(guān)于condition
屬性的方法氨距,只有condition
參數(shù)與初始化時(shí)候的condition
相等桑逝,lock
才能正確進(jìn)行加鎖操作。而unlockWithCondition:
并不是當(dāng)Condition
符合條件時(shí)才解鎖俏让,而是解鎖之后肢娘,修改Condition
的值,這個(gè)結(jié)論可以從下面的例子中得出舆驶。
//主線程中
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
//線程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lockWhenCondition:1];
NSLog(@"線程1");
sleep(2);
[lock unlock];
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保證讓線程2的代碼后執(zhí)行
if ([lock tryLockWhenCondition:0]) {
NSLog(@"線程2");
[lock unlockWithCondition:2];
NSLog(@"線程2解鎖成功");
} else {
NSLog(@"線程2嘗試加鎖失敗");
}
});
//線程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);//以保證讓線程2的代碼后執(zhí)行
if ([lock tryLockWhenCondition:2]) {
NSLog(@"線程3");
[lock unlock];
NSLog(@"線程3解鎖成功");
} else {
NSLog(@"線程3嘗試加鎖失敗");
}
});
//線程4
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);//以保證讓線程2的代碼后執(zhí)行
if ([lock tryLockWhenCondition:2]) {
NSLog(@"線程4");
[lock unlockWithCondition:1];
NSLog(@"線程4解鎖成功");
} else {
NSLog(@"線程4嘗試加鎖失敗");
}
});
// 線程2
// 線程2解鎖成功
// 線程3
// 線程3解鎖成功
// 線程4
// 線程4解鎖成功
// 線程1
- 從上面可以得出橱健,
NSConditionLock
還可以實(shí)現(xiàn)任務(wù)之間的依賴。
信號(hào)量
dispatch_semaphore
//傳入的參數(shù)為long沙廉,輸出一個(gè)dispatch_semaphore_t類型且值為value的信號(hào)量拘荡。
//值得注意的是,這里的傳入的參數(shù)value必須大于或等于0撬陵,否則dispatch_semaphore_create會(huì)返回NULL珊皿。
dispatch_semaphore_create(long value);
//這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值減1;
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
//這個(gè)函數(shù)會(huì)使傳入的信號(hào)量dsema的值加1巨税;
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
-
dispatch_semaphore
是GCD
用來同步的一種方式蟋定,與他相關(guān)的只有三個(gè)函數(shù),一個(gè)是創(chuàng)建信號(hào)量草添,一個(gè)是等待信號(hào)驶兜,一個(gè)是發(fā)送信號(hào)。 - 這個(gè)函數(shù)的作用是這樣的
- 如果
dsema
信號(hào)量的值大于0
,該函數(shù)所處線程就繼續(xù)執(zhí)行下面的語句抄淑,并且將信號(hào)量的值減1
屠凶; - 如果
desema
的值為0
,那么這個(gè)函數(shù)就阻塞當(dāng)前線程等待timeout
(注意timeout
的類型為dispatch_time_t
肆资,不能直接傳入整形或float
型數(shù)) - 如果等待的期間
desema
的值被dispatch_semaphore_signal
函數(shù)加1
了矗愧,且該函數(shù)(即dispatch_semaphore_wait
)所處線程獲得了信號(hào)量,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1
郑原。 - 如果等待期間沒有獲取到信號(hào)量或者信號(hào)量的值一直為
0
唉韭,那么等到timeout
時(shí),其所處線程自動(dòng)執(zhí)行其后語句犯犁。
- 如果
-
dispatch_semaphore
是信號(hào)量属愤,但當(dāng)信號(hào)總量設(shè)為1
時(shí)也可以當(dāng)作鎖來。在沒有等待情況出現(xiàn)時(shí)栖秕,它的性能比pthread_mutex
還要高春塌,但一旦有等待情況出現(xiàn)時(shí)晓避,性能就會(huì)下降許多簇捍。相對(duì)于OSSpinLock
來說,它的優(yōu)勢在于等待時(shí)不會(huì)消耗CPU
資源俏拱。
// 超時(shí)時(shí)間overTime設(shè)置成>2
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要線程同步的操作1 開始");
sleep(2);
NSLog(@"需要線程同步的操作1 結(jié)束");
dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(signal, overTime);
NSLog(@"需要線程同步的操作2");
dispatch_semaphore_signal(signal);
});
// 需要線程同步的操作1 開始
// 需要線程同步的操作1 結(jié)束
// 需要線程同步的操作2
// 超時(shí)時(shí)間設(shè)置為<2s的時(shí)候
//...
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
//...
// 需要線程同步的操作1 開始
// 需要線程同步的操作2
// 需要線程同步的操作1 結(jié)束
- 如上的代碼暑塑,如果超時(shí)時(shí)間
overTime
設(shè)置成>2
,可完成同步操作锅必。如果overTime<2
的話事格,在線程1
還沒有執(zhí)行完成的情況下,此時(shí)超時(shí)了搞隐,將自動(dòng)執(zhí)行下面的代碼驹愚。
dispatch_semaphore
和NSCondition
類似,都是一種基于信號(hào)的同步方式劣纲,但NSCondition
信號(hào)只能發(fā)送逢捺,不能保存(如果沒有線程在等待,則發(fā)送的信號(hào)會(huì)失效)癞季。而dispatch_semaphore
能保存發(fā)送的信號(hào)劫瞳。dispatch_semaphore
的核心是dispatch_semaphore_t
類型的信號(hào)量。
-
dispatch_semaphore_create(1)
方法可以創(chuàng)建一個(gè)dispatch_semaphore_t
類型的信號(hào)量绷柒,設(shè)定信號(hào)量的初始值為1
志于。注意,這里的傳入的參數(shù)必須大于或等于0
废睦,否則dispatch_semaphore_create
會(huì)返回NULL
伺绽。 -
dispatch_semaphore_wait(signal, overTime);
方法會(huì)判斷signal
的信號(hào)值是否大于0
。大于0
不會(huì)阻塞線程,消耗掉一個(gè)信號(hào)憔恳,執(zhí)行后續(xù)任務(wù)瓤荔。- 如果信號(hào)值為
0
,該線程會(huì)和NSCondition
一樣直接進(jìn)入waiting
狀態(tài)钥组,等待其他線程發(fā)送信號(hào)喚醒線程去執(zhí)行后續(xù)任務(wù)输硝,或者當(dāng)overTime
時(shí)限到了,也會(huì)執(zhí)行后續(xù)任務(wù)程梦。
- 如果信號(hào)值為
-
dispatch_semaphore_signal(signal);
發(fā)送信號(hào)点把,如果沒有等待的線程接受信號(hào),則使signal
信號(hào)值加1
(做到對(duì)信號(hào)的保存)屿附。 - 和
NSLock
的lock
和unlock
類似郎逃,區(qū)別只在于有信號(hào)量這個(gè)參數(shù),lock
unlock
只能同一時(shí)間挺份,一個(gè)線程訪問被保護(hù)的臨界區(qū)褒翰,而如果dispatch_semaphore
的信號(hào)量初始值為x
,則可以有x
個(gè)線程同時(shí)訪問被保護(hù)的臨界區(qū)匀泊。
補(bǔ)充
pthread_cleanup_push()
& pthread_cleanup_pop()
- 線程是允許在退出的時(shí)候优训,調(diào)用一些回調(diào)方法的。如果你需要做類似的事情各聘,那么就用以下這兩種方法:
void pthread_cleanup_push(void (*callback)(void *), void *arg);
void pthread_cleanup_pop(int execute);
- 正如名字所暗示的揣非,它背后有一個(gè)
stack
,你可以塞很多個(gè)callback
函數(shù)進(jìn)去躲因,然后調(diào)用的時(shí)候按照先入后出的順序調(diào)用這些callback
早敬。所以你在塞callback
的時(shí)候,如果是關(guān)心調(diào)用順序的大脉,那就得注意這一點(diǎn)了搞监。 - 但是!你塞進(jìn)去的callback只有在以下情況下才會(huì)被調(diào)用:
- 線程通過
pthread_exit()
函數(shù)退出 - 線程被
pthread_cancel()
取消 -
pthread_cleanup_pop(int execute)
時(shí)镰矿,execute
傳了一個(gè)非0
值
- 線程通過
- 也就是說琐驴,如果你的線程函數(shù)是這么寫的,那在線程結(jié)束的時(shí)候就不會(huì)調(diào)到你塞進(jìn)去的那些
callback
了:
static void * thread_function(void *args)
{
...
...
...
...
return 0; // 線程退出時(shí)沒有調(diào)用pthread_exit()退出衡怀,而是直接return棍矛,此時(shí)是不會(huì)調(diào)用棧內(nèi)callback的
}
-
pthread_cleanup_push
塞入的callback
可以用來記錄線程結(jié)束的點(diǎn),般不太會(huì)在這里執(zhí)行業(yè)務(wù)邏輯抛杨。在線程結(jié)束之后如果要執(zhí)行業(yè)務(wù)邏輯够委,一般用下面提到的pthread_join
。
注意事項(xiàng):callback
函數(shù)是可以傳參數(shù)的
- 在
pthread_cleanup_push
函數(shù)中怖现,第二個(gè)參數(shù)的值會(huì)作為callback
函數(shù)的第一個(gè)參數(shù)茁帽,拿來打打日志也不錯(cuò)
void callback(void *callback_arg)
{
printf("arg is : %s\n", (char *)callback_arg);
}
static void * thread_function(void *thread_arg)
{
...
pthread_cleanup_push(callback, "this is a queue thread, and was terminated.");
...
pthread_exit((void *) 0); // 這句不調(diào)用玉罐,線程結(jié)束就不會(huì)調(diào)用你塞進(jìn)去的callback函數(shù)。
return ((void *) 0);
}
int main ()
{
...
...
error = pthread_create(&tid, NULL, thread_function, (void *)thread_arg)
...
...
return 0;
}
要保持callback棧平衡
pthread_cleanup_pop(0); // 傳遞參數(shù)0潘拨,在pop的時(shí)候就不會(huì)調(diào)用對(duì)應(yīng)的callback吊输,如果傳遞非0值剩拢,pop的時(shí)候就會(huì)調(diào)用對(duì)應(yīng)callback了稠炬。
pthread_cleanup_pop(0); // push了兩次就pop兩次,你要是只pop一次就不能編譯通過
-
pthread
對(duì)于這兩個(gè)函數(shù)是通過宏來實(shí)現(xiàn)的妈拌,如果沒有一一對(duì)應(yīng)章郁,編譯器就會(huì)報(bào)} missing
的錯(cuò)誤。其相關(guān)實(shí)現(xiàn)代碼如下:
/* ./include/pthread/pthread.h */
#define pthread_cleanup_push(rt, rtarg) __pthread_cleanup_push(rt, rtarg)
#define pthread_cleanup_pop(execute) __pthread_cleanup_pop(execute)
/* ./sysdeps/generic/bits/cancelation.h */
#define __pthread_cleanup_push(rt, rtarg) \
{ \
struct __pthread_cancelation_handler **__handlers \
= __pthread_get_cleanup_stack (); \
struct __pthread_cancelation_handler __handler = \
{ \
(rt), \
(rtarg), \
*__handlers \
}; \
*__handlers = &__handler;
#define __pthread_cleanup_pop(execute) \
if (execute) \
__handler.handler (__handler.arg); \
*__handlers = __handler.next; \
} \
具體細(xì)節(jié)請參考推薦文章