1. 什么情況下會(huì)有線(xiàn)程隱患访递?
我們?cè)谑褂枚嗑€(xiàn)程技術(shù)帶來(lái)的便利的同時(shí),也需要考慮下多線(xiàn)程所帶來(lái)的隱患唯袄。比如,我們可以并發(fā)
的進(jìn)行多個(gè)任務(wù)蜗帜,因此同一塊資源就可能在多個(gè)線(xiàn)程中同時(shí)被訪(fǎng)問(wèn)(讀/寫(xiě))
恋拷,這個(gè)現(xiàn)象叫做資源共享
,比如多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)了同一個(gè)對(duì)象\變量\文件厅缺,這樣就有可能引發(fā)數(shù)據(jù)錯(cuò)亂
和數(shù)據(jù)安全
問(wèn)題蔬顾。
2. 兩個(gè)常見(jiàn)的示例
2.1 示例:存錢(qián)取錢(qián)
我們用代碼來(lái)實(shí)現(xiàn)一下這個(gè)功能:
- (void)moneyTest{
self.money = 1000;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
// 存錢(qián)
- (void)saveMoney{
NSInteger oldMoney = self.money;
sleep(0.5);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存了50元,賬戶(hù)余額 %ld湘捎,當(dāng)前線(xiàn)程 %@",self.money,[NSThread currentThread]);
}
// 取錢(qián)
- (void)drawMoney{
NSInteger oldMoney = self.money;
sleep(0.5);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取了20元诀豁,賬戶(hù)余額 %ld,當(dāng)前線(xiàn)程 %@",self.money,[NSThread currentThread]);
}
輸出結(jié)果:
2019-10-12 16:40:18.033330+0800 tianran[16615:4423457] 存了50元窥妇,賬戶(hù)余額 1050且叁,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033486+0800 tianran[16615:4423457] 存了50元,賬戶(hù)余額 1080秩伞,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033500+0800 tianran[16615:4423456] 取了20元逞带,賬戶(hù)余額 1030欺矫,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.033573+0800 tianran[16615:4423457] 存了50元,賬戶(hù)余額 1130展氓,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033632+0800 tianran[16615:4423456] 取了20元穆趴,賬戶(hù)余額 1110,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.033659+0800 tianran[16615:4423457] 存了50元遇汞,賬戶(hù)余額 1160未妹,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033730+0800 tianran[16615:4423456] 取了20元,賬戶(hù)余額 1140空入,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.033737+0800 tianran[16615:4423457] 存了50元络它,賬戶(hù)余額 1190,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033821+0800 tianran[16615:4423457] 存了50元歪赢,賬戶(hù)余額 1240化戳,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.033839+0800 tianran[16615:4423456] 取了20元,賬戶(hù)余額 1170埋凯,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.033949+0800 tianran[16615:4423457] 存了50元点楼,賬戶(hù)余額 1220,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.034040+0800 tianran[16615:4423456] 取了20元白对,賬戶(hù)余額 1200掠廓,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.034178+0800 tianran[16615:4423457] 存了50元,賬戶(hù)余額 1250甩恼,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.034341+0800 tianran[16615:4423456] 取了20元蟀瞧,賬戶(hù)余額 1230,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.034423+0800 tianran[16615:4423457] 存了50元条摸,賬戶(hù)余額 1280悦污,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.034581+0800 tianran[16615:4423456] 取了20元,賬戶(hù)余額 1260屈溉,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.034665+0800 tianran[16615:4423457] 存了50元,賬戶(hù)余額 1310抬探,當(dāng)前線(xiàn)程 <NSThread: 0x2829d1840>{number = 3, name = (null)}
2019-10-12 16:40:18.034803+0800 tianran[16615:4423456] 取了20元子巾,賬戶(hù)余額 1290,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.035029+0800 tianran[16615:4423456] 取了20元小压,賬戶(hù)余額 1270线梗,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
2019-10-12 16:40:18.035105+0800 tianran[16615:4423456] 取了20元,賬戶(hù)余額 1250怠益,當(dāng)前線(xiàn)程 <NSThread: 0x2829d91c0>{number = 4, name = (null)}
我們?cè)?code>moneyTest方法中仪搔,以多線(xiàn)程方式分別進(jìn)行了10次
的存/取錢(qián)操作,每次存50
蜻牢,每次取20
烤咧,最后執(zhí)行完之后余額應(yīng)該為1000 + (50 * 10) - (20 * 10) = 1300
偏陪,但是上面輸出的結(jié)果是1250
,很明顯煮嫌,出大問(wèn)題了笛谦。
2.2 示例:賣(mài)票問(wèn)題
我們通過(guò)代碼展示一下這個(gè)問(wèn)題:
- (void)ticketTest{
self.ticketNum = 30;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
// 賣(mài)票操作
- (void)saleTicket{
self.ticketNum -= 1;
NSLog(@"還剩%ld張票,currentThread - %@",self.ticketNum,[NSThread currentThread]);
}
輸出結(jié)果:
2019-10-12 16:46:38.501449+0800 tianran[16620:4425096] 還剩29張票昌阿,currentThread - <NSThread: 0x28335a9c0>{number = 5, name = (null)}
2019-10-12 16:46:38.501573+0800 tianran[16620:4425094] 還剩28張票饥脑,currentThread - <NSThread: 0x2833530c0>{number = 3, name = (null)}
2019-10-12 16:46:38.501593+0800 tianran[16620:4425096] 還剩27張票,currentThread - <NSThread: 0x28335a9c0>{number = 5, name = (null)}
2019-10-12 16:46:38.501684+0800 tianran[16620:4425096] 還剩25張票懦冰,currentThread - <NSThread: 0x28335a9c0>{number = 5, name = (null)}
2019-10-12 16:46:38.501693+0800 tianran[16620:4425094] 還剩24張票灶轰,currentThread - <NSThread: 0x2833530c0>{number = 3, name = (null)}
2019-10-12 16:46:38.501689+0800 tianran[16620:4425093] 還剩26張票,currentThread - <NSThread: 0x28336aa40>{number = 6, name = (null)}
2019-10-12 16:46:38.501753+0800 tianran[16620:4425096] 還剩23張票刷钢,currentThread - <NSThread: 0x28335a9c0>{number = 5, name = (null)}
2019-10-12 16:46:38.501776+0800 tianran[16620:4425094] 還剩22張票笋颤,currentThread - <NSThread: 0x2833530c0>{number = 3, name = (null)}
2019-10-12 16:46:38.501781+0800 tianran[16620:4425093] 還剩21張票,currentThread - <NSThread: 0x28336aa40>{number = 6, name = (null)}
2019-10-12 16:46:38.501877+0800 tianran[16620:4425094] 還剩19張票闯捎,currentThread - <NSThread: 0x2833530c0>{number = 3, name = (null)}
2019-10-12 16:46:38.501819+0800 tianran[16620:4425096] 還剩20張票椰弊,currentThread - <NSThread: 0x28335a9c0>{number = 5, name = (null)}
2019-10-12 16:46:38.502044+0800 tianran[16620:4425093] 還剩18張票,currentThread - <NSThread: 0x28336aa40>{number = 6, name = (null)}
2019-10-12 16:46:38.502404+0800 tianran[16620:4425094] 還剩17張票瓤鼻,currentThread - <NSThread: 0x2833530c0>{number = 3, name = (null)}
2019-10-12 16:46:38.502529+0800 tianran[16620:4425093] 還剩16張票秉版,currentThread - <NSThread: 0x28336aa40>{number = 6, name = (null)}
2019-10-12 16:46:38.502760+0800 tianran[16620:4425093] 還剩15張票,currentThread - <NSThread: 0x28336aa40>{number = 6, name = (null)}
雖然最后的結(jié)果是一樣的茬祷,但是我們可以看到中間賣(mài)票的時(shí)候清焕,票數(shù)有問(wèn)題了。
上面兩個(gè)問(wèn)題都是由于多個(gè)線(xiàn)程對(duì)同一資源
進(jìn)行了讀寫(xiě)操作
而導(dǎo)致的祭犯,下面用一個(gè)熟悉的圖片來(lái)表示下:
針對(duì)這個(gè)問(wèn)題的解決方案:使用線(xiàn)程同步
技術(shù)(同步秸妥,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)沃粗。常見(jiàn)的線(xiàn)程同步技術(shù)就是:加鎖
粥惧。
如下圖所示:
在進(jìn)行操作的時(shí)候,先加鎖最盅,保證此時(shí)只有一個(gè)線(xiàn)程對(duì)資源進(jìn)行操作突雪,操作完成后在解鎖。
3. 線(xiàn)程同步(加鎖)方案
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
借鑒一張鎖的性能數(shù)據(jù)對(duì)比圖涡贱,如下所示:
介紹鎖之前咏删,我們可以先看幾個(gè)概念定義:
臨界區(qū):
指的是一塊對(duì)公共資源
進(jìn)行訪(fǎng)問(wèn)的代碼,并非一種機(jī)制或算法问词。自旋鎖:
用于多線(xiàn)程同步的一種鎖督函,線(xiàn)程反復(fù)檢查鎖變量是否可用
。由于線(xiàn)程在這一過(guò)程中保持執(zhí)行,因此是一種忙等待
辰狡。一旦獲取了自旋鎖锋叨,線(xiàn)程會(huì)一直保持該鎖,直至顯式的釋放
自旋鎖搓译。自旋鎖避免了線(xiàn)程上下文的調(diào)度開(kāi)銷(xiāo)
悲柱,因此對(duì)于線(xiàn)程只會(huì)阻塞很短時(shí)間的場(chǎng)景
是非常有效的,如上面的OSSpinLock
和atomic
些己。互斥鎖(
Mutex
):
是一種用于多線(xiàn)程編程中豌鸡,防止多個(gè)線(xiàn)程同時(shí)對(duì)同一公共資源(比如全局變量)進(jìn)行讀寫(xiě)的機(jī)制。該目的通過(guò)將代碼切片成一個(gè)一個(gè)的臨界區(qū)
而達(dá)成段标,如上面的@ synchronized涯冠、NSLock 、pthread_mutex
逼庞。條件鎖
條件鎖就是條件變量
蛇更,當(dāng)進(jìn)程的某些資源要求不滿(mǎn)足時(shí)就進(jìn)入休眠
,即鎖住了赛糟,當(dāng)資源被分配到了派任,條件鎖打開(kāi)了,進(jìn)程繼續(xù)運(yùn)行璧南,如上面的NSCondition
掌逛、NSConditionLock
遞歸鎖
遞歸鎖就是同一個(gè)線(xiàn)程可以加鎖 N 次而不會(huì)引發(fā)死鎖。遞歸鎖是特殊的互斥鎖司倚,即帶有遞歸性質(zhì)的互斥鎖豆混,如:pthread_mutex(recursive)
、NSRecursiveLock
动知。讀寫(xiě)鎖:
是計(jì)算機(jī)程序的并發(fā)控制的一種同步機(jī)制皿伺,也稱(chēng)“共享-互斥鎖”或者“多讀-單寫(xiě)鎖”陆赋,是一種特殊的自旋鎖
市咽。用于解決多線(xiàn)程對(duì)公共資源讀寫(xiě)問(wèn)題。讀操作可并發(fā)重入萄凤,寫(xiě)操作是互斥的丹皱。讀寫(xiě)鎖通常用互斥鎖妒穴、條件變量、信號(hào)量實(shí)現(xiàn)种呐。信號(hào)量(semaphore):
是一種更高級(jí)的同步機(jī)制
宰翅,互斥鎖
可以說(shuō)是semaphore
在僅取值0/1
時(shí)的特例弃甥。信號(hào)量可以有更多的取值空間爽室,用來(lái)實(shí)現(xiàn)更加復(fù)雜的同步,而不單單是線(xiàn)程間互斥,如:dispatch_semaphore
阔墩。
其實(shí)基本的鎖就包括三類(lèi):自旋鎖嘿架、互斥鎖、讀寫(xiě)鎖啸箫,其他的比如條件鎖耸彪、遞歸鎖、信號(hào)量都是上層的封裝和實(shí)現(xiàn)忘苛。
3.1 OSSpinLock
OSSpinLock叫做“自旋鎖”蝉娜,需要導(dǎo)入頭文件
#import <libkern/OSAtomic.h>
常用的API:
OSSpinLock lock = OS_UNFAIR_LOCK_INIT; ——初始化鎖對(duì)象lock
OSSpinLockTry(&lock);——嘗試加鎖,加鎖成功繼續(xù)扎唾,加鎖失敗返回召川,繼續(xù)執(zhí)行后面的代碼,不阻塞線(xiàn)程
OSSpinLockLock(&lock);——加鎖胸遇,加鎖失敗會(huì)阻塞線(xiàn)程荧呐,進(jìn)行等待
OSSpinLockUnlock(&lock);——解鎖
我們使用上面的第二個(gè)賣(mài)票示例來(lái)進(jìn)行加鎖操作:
#import <libkern/OSAtomic.h>
@interface ViewController ()
// 票總數(shù)
@property (nonatomic, assign) NSInteger ticketNum;
// 鎖
@property (nonatomic, assign) OSSpinLock lock;
@end
- (void)ticketTest{
// 初始化鎖
self.lock = OS_SPINLOCK_INIT;
self.ticketNum = 30;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
// 賣(mài)票操作
- (void)saleTicket{
// 加鎖????
OSSpinLockLock(&_lock);
self.ticketNum -= 1;
NSLog(@"還剩%ld張票,currentThread - %@",self.ticketNum,[NSThread currentThread]);
// 解鎖????
OSSpinLockUnlock(&_lock);
}
結(jié)果輸出:
2019-10-12 17:02:55.774748+0800 tianran[16628:4429441] 還剩29張票纸镊,currentThread - <NSThread: 0x2834ed5c0>{number = 5, name = (null)}
2019-10-12 17:02:55.774955+0800 tianran[16628:4429445] 還剩28張票倍阐,currentThread - <NSThread: 0x28341c080>{number = 6, name = (null)}
2019-10-12 17:02:55.775065+0800 tianran[16628:4429445] 還剩27張票,currentThread - <NSThread: 0x28341c080>{number = 6, name = (null)}
2019-10-12 17:02:55.775156+0800 tianran[16628:4429443] 還剩26張票逗威,currentThread - <NSThread: 0x28349a540>{number = 3, name = (null)}
2019-10-12 17:02:55.775237+0800 tianran[16628:4429443] 還剩25張票峰搪,currentThread - <NSThread: 0x28349a540>{number = 3, name = (null)}
2019-10-12 17:02:55.775301+0800 tianran[16628:4429443] 還剩24張票,currentThread - <NSThread: 0x28349a540>{number = 3, name = (null)}
2019-10-12 17:02:55.775368+0800 tianran[16628:4429443] 還剩23張票庵楷,currentThread - <NSThread: 0x28349a540>{number = 3, name = (null)}
2019-10-12 17:02:55.775433+0800 tianran[16628:4429443] 還剩22張票罢艾,currentThread - <NSThread: 0x28349a540>{number = 3, name = (null)}
2019-10-12 17:02:55.775513+0800 tianran[16628:4429445] 還剩21張票,currentThread - <NSThread: 0x28341c080>{number = 6, name = (null)}
2019-10-12 17:02:55.775578+0800 tianran[16628:4429445] 還剩20張票尽纽,currentThread - <NSThread: 0x28341c080>{number = 6, name = (null)}
2019-10-12 17:02:55.775643+0800 tianran[16628:4429445] 還剩19張票咐蚯,currentThread - <NSThread: 0x28341c080>{number = 6, name = (null)}
2019-10-12 17:02:55.775717+0800 tianran[16628:4429441] 還剩18張票,currentThread - <NSThread: 0x2834ed5c0>{number = 5, name = (null)}
2019-10-12 17:02:55.775781+0800 tianran[16628:4429441] 還剩17張票弄贿,currentThread - <NSThread: 0x2834ed5c0>{number = 5, name = (null)}
2019-10-12 17:02:55.775911+0800 tianran[16628:4429441] 還剩16張票春锋,currentThread - <NSThread: 0x2834ed5c0>{number = 5, name = (null)}
2019-10-12 17:02:55.775983+0800 tianran[16628:4429441] 還剩15張票,currentThread - <NSThread: 0x2834ed5c0>{number = 5, name = (null)}
這里要注意差凹,我們使用的是同一個(gè)鎖對(duì)象
期奔,如果用多個(gè),肯定也沒(méi)效果啊危尿。
賣(mài)票的問(wèn)題呐萌,我們針對(duì)的是同一個(gè)操作來(lái)處理的,而存錢(qián)取錢(qián)的問(wèn)題谊娇,涉及到了兩個(gè)操作(存錢(qián)和取錢(qián))肺孤,首先要明確問(wèn)題,加鎖機(jī)制是為了解決多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)共享資源
所產(chǎn)生的數(shù)據(jù)問(wèn)題,無(wú)論這些線(xiàn)程里面執(zhí)行的是什么操作赠堵,如果這些操作影響了共享資源小渊,不能同時(shí)進(jìn)行的話(huà),那么應(yīng)該對(duì)這些操作使用同一個(gè)鎖對(duì)象進(jìn)行加鎖茫叭。
所以酬屉,我們應(yīng)該對(duì)存錢(qián)操作
和取錢(qián)操作
使用相同的鎖對(duì)象
進(jìn)行加鎖。
代碼如下:
#import <libkern/OSAtomic.h>
@interface ViewController ()
// 總錢(qián)數(shù)
@property (nonatomic, assign) NSInteger money;
// 鎖
@property (nonatomic, assign) OSSpinLock lock;
@end
- (void)moneyTest{
// 初始化鎖
self.lock = OS_SPINLOCK_INIT;
self.money = 1000;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[self saveMoney];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
// 存錢(qián)
- (void)saveMoney{
sleep(0.5);
OSSpinLockLock(&_lock);
NSInteger oldMoney = self.money;
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存了50元揍愁,賬戶(hù)余額 %ld呐萨,當(dāng)前線(xiàn)程 %@",self.money,[NSThread currentThread]);
OSSpinLockUnlock(&_lock);
}
// 取錢(qián)
- (void)drawMoney{
sleep(0.5);
OSSpinLockLock(&_lock);
NSInteger oldMoney = self.money;
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取了20元,賬戶(hù)余額 %ld莽囤,當(dāng)前線(xiàn)程 %@",self.money,[NSThread currentThread]);
OSSpinLockUnlock(&_lock);
}
輸出結(jié)果:
2019-10-12 17:14:44.362398+0800 tianran[16653:4433835] 存了50元垛吗,賬戶(hù)余額 1050,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.362540+0800 tianran[16653:4433832] 取了20元烁登,賬戶(hù)余額 1030怯屉,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.362640+0800 tianran[16653:4433835] 存了50元,賬戶(hù)余額 1080饵沧,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.362711+0800 tianran[16653:4433832] 取了20元锨络,賬戶(hù)余額 1060,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.362790+0800 tianran[16653:4433835] 存了50元狼牺,賬戶(hù)余額 1110羡儿,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.362923+0800 tianran[16653:4433832] 取了20元,賬戶(hù)余額 1090是钥,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.363015+0800 tianran[16653:4433835] 存了50元掠归,賬戶(hù)余額 1140,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363078+0800 tianran[16653:4433832] 取了20元悄泥,賬戶(hù)余額 1120虏冻,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.363152+0800 tianran[16653:4433835] 存了50元,賬戶(hù)余額 1170弹囚,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363206+0800 tianran[16653:4433832] 取了20元厨相,賬戶(hù)余額 1150,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.363278+0800 tianran[16653:4433835] 存了50元鸥鹉,賬戶(hù)余額 1200蛮穿,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363335+0800 tianran[16653:4433832] 取了20元,賬戶(hù)余額 1180毁渗,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.363405+0800 tianran[16653:4433835] 存了50元践磅,賬戶(hù)余額 1230,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363458+0800 tianran[16653:4433832] 取了20元灸异,賬戶(hù)余額 1210府适,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.363555+0800 tianran[16653:4433835] 存了50元幻碱,賬戶(hù)余額 1260,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363723+0800 tianran[16653:4433835] 存了50元细溅,賬戶(hù)余額 1310,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363770+0800 tianran[16653:4433835] 存了50元儡嘶,賬戶(hù)余額 1360喇聊,當(dāng)前線(xiàn)程 <NSThread: 0x28118a780>{number = 5, name = (null)}
2019-10-12 17:14:44.363889+0800 tianran[16653:4433832] 取了20元,賬戶(hù)余額 1340蹦狂,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.364014+0800 tianran[16653:4433832] 取了20元誓篱,賬戶(hù)余額 1320,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
2019-10-12 17:14:44.364129+0800 tianran[16653:4433832] 取了20元凯楔,賬戶(hù)余額 1300窜骄,當(dāng)前線(xiàn)程 <NSThread: 0x281189080>{number = 3, name = (null)}
這次就沒(méi)問(wèn)題了,過(guò)程和結(jié)果都沒(méi)有問(wèn)題摆屯。
上面有提到邻遏,OSSpinLock
是自旋鎖
,那么為什么叫做自旋鎖呢虐骑?
自旋鎖的原理是當(dāng)已經(jīng)被別的線(xiàn)程加鎖了准验,加鎖失敗的時(shí)候,讓線(xiàn)程處于忙等
的狀態(tài)廷没,以此讓線(xiàn)程停留在臨界區(qū)
(需要加鎖的代碼段)之外糊饱,一旦加鎖成功,線(xiàn)程便可以進(jìn)入臨界區(qū)進(jìn)行對(duì)共享資源操作颠黎。
讓線(xiàn)程阻塞有兩種方法:
- 讓線(xiàn)程休眠另锋,
RunLoop
里面用到的mach_msg()
實(shí)現(xiàn)的效果就是這一種,它借助系統(tǒng)內(nèi)核指令狭归,??線(xiàn)程停下來(lái)夭坪,CPU不再分配資源給線(xiàn)程,因此不會(huì)再執(zhí)行任何一句匯編指令过椎,我們后面要介紹的互斥所台舱,也是屬于這種,它的底層匯編調(diào)用了一個(gè)系統(tǒng)函數(shù)syscall
使得線(xiàn)程進(jìn)入休眠 - 自旋鎖的忙等潭流,本質(zhì)上是一個(gè)
while循環(huán)
竞惋,不斷的去判斷加鎖條件,一旦當(dāng)前已經(jīng)進(jìn)入臨界區(qū)(加鎖代碼塊)的線(xiàn)程完成了操作灰嫉,解開(kāi)鎖之后拆宛,等待鎖的線(xiàn)程便可以成功加鎖,再次進(jìn)入臨界區(qū)讼撒。自旋鎖其實(shí)并沒(méi)有真正讓線(xiàn)程停下來(lái)股耽,線(xiàn)程只不過(guò)是暫時(shí)被困在while循環(huán)里面,CPU還是在不斷的分配資源去處理它的匯編指令的(while循環(huán)的匯編指令)钳幅。
現(xiàn)在蘋(píng)果已經(jīng)建議開(kāi)發(fā)者停止使用OSSpinLock
了物蝙,在線(xiàn)程優(yōu)先級(jí)的作用下,會(huì)產(chǎn)生【優(yōu)先級(jí)反轉(zhuǎn)】
敢艰,使得自旋鎖卡住诬乞,因此它不再安全了。
可以看不再安全的OSSpinLock钠导。
我們知道震嫉,計(jì)算機(jī)的CPU
在同一時(shí)間,只能處理一條線(xiàn)程牡属,對(duì)于單核``CPU
來(lái)說(shuō)票堵,線(xiàn)程的并發(fā),實(shí)際上是一種假象逮栅,是系統(tǒng)讓CPU
一很小的時(shí)間間隔在線(xiàn)程之間來(lái)回切換
悴势,所以看上去多條線(xiàn)程好像是在同時(shí)進(jìn)行的。到了多核CPU
時(shí)代措伐,確實(shí)可以實(shí)現(xiàn)真正的線(xiàn)程并發(fā)瞳浦,但是CPU核心數(shù)
畢竟是有限的,而程序內(nèi)部的線(xiàn)程數(shù)量通撤鲜浚肯定是遠(yuǎn)大于CPU數(shù)量
的叫潦,因此,很多情況下我們面對(duì)的還是單CPU處理多線(xiàn)程的情況官硝〈H铮基于這種場(chǎng)景,需要了解一個(gè)概念叫做線(xiàn)程優(yōu)先級(jí)
氢架,CPU會(huì)將盡可能多的時(shí)間(資源)
分配給優(yōu)先級(jí)高的線(xiàn)程
傻咖,我們用下圖來(lái)展示一下所謂的優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題:
低優(yōu)先級(jí)
的線(xiàn)程A
獲得鎖并訪(fǎng)問(wèn)共享資源,這時(shí)一個(gè)高優(yōu)先級(jí)
的線(xiàn)程B
也嘗試獲得這個(gè)鎖岖研,它會(huì)處于spin lock的忙等狀態(tài)
從而占用大量CPU卿操,此時(shí)低優(yōu)先級(jí)的線(xiàn)程A
無(wú)法與高優(yōu)先級(jí)線(xiàn)程B
爭(zhēng)奪CPU時(shí)間,從而導(dǎo)致線(xiàn)程A
任務(wù)遲遲完不成孙援,無(wú)法釋放lock
害淤,所以會(huì)出現(xiàn)卡住的問(wèn)題。
自旋鎖的while循環(huán)
本質(zhì)拓售,使得線(xiàn)程并沒(méi)有停下來(lái)窥摄,一般情況下,一條線(xiàn)程等待鎖的時(shí)間不會(huì)太長(zhǎng)础淤,選用自旋鎖來(lái)阻塞線(xiàn)程所消耗的CPU資源崭放,要小于線(xiàn)程的休眠和喚醒所帶來(lái)的CPU資源開(kāi)銷(xiāo)哨苛,因此自選鎖是一種效率很高
的加鎖機(jī)制,但是優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題
使得自旋鎖不再安全币砂,鎖的最終目的是安全不是效率建峭,所以蘋(píng)果放棄了OSSpinLock
。
在iOS 10/macOS 10.12
發(fā)布時(shí)决摧,蘋(píng)果提供了新的 os_unfair_lock
作為 OSSpinLock
的替代亿蒸,并且將OSSpinLock
標(biāo)記為了 Deprecated
。
另外為什么RunLoop
要選擇真正的線(xiàn)程休眠
呢蜜徽?因?yàn)閷?duì)于A(yíng)pp來(lái)說(shuō),可能處于長(zhǎng)時(shí)間的擱置狀態(tài)票摇,而沒(méi)有任何用戶(hù)行為發(fā)生拘鞋,不需要CPU管,對(duì)于這種場(chǎng)景矢门,當(dāng)然是讓線(xiàn)程休眠更為節(jié)約性能盆色。
3.2 os_unfair_lock
蘋(píng)果建議從iOS10.0
之后,使用os_unfair_lock
代替OSSpinLock
祟剔,現(xiàn)在我們就去看一下如何使用:
先導(dǎo)入頭文件
#import <os/lock.h>
使用的API:
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
初始化鎖對(duì)象lock
os_unfair_lock_trylock(&lock);
嘗試加鎖隔躲,加鎖成功繼續(xù),加鎖失敗返回物延,繼續(xù)執(zhí)行后面的代碼宣旱,不阻塞線(xiàn)程
os_unfair_lock_lock(&lock);
加鎖,加鎖失敗會(huì)阻塞線(xiàn)程進(jìn)行等待
os_unfair_lock_unlock(&lock);
解鎖
它的使用和OSSpinLock
的方法一樣叛薯,這里就不再舉例了浑吟,蘋(píng)果為了解決OSSpinLock
的優(yōu)先級(jí)反轉(zhuǎn)問(wèn)題,在os_unfair_lock
中摒棄了忙等方式耗溜,使用讓線(xiàn)程真正休眠
的方式组力,來(lái)阻塞線(xiàn)程,也就從根本上解決了問(wèn)題抖拴。
3.3 atomic
atomic
適用于OC
中屬性的修飾符燎字,其自帶一把互斥鎖
,但是這個(gè)一般基本不使用阿宅,都是使用的nonatomic
候衍。
setter
方法會(huì)根據(jù)修飾符調(diào)用不同的方法,其中最后會(huì)統(tǒng)一調(diào)用 reallySetProperty
方法洒放,其中就有對(duì)于atomic
和非 atomic
的操作:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
id *slot = (id*) ((char*)self + offset);
...
if (!atomic) {//未加鎖
oldValue = *slot;
*slot = newValue;
} else {//加鎖
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
...
}
從源碼中可以看出脱柱,對(duì)于atomic修飾
的屬性,進(jìn)行了spinlock_t
加鎖處理拉馋,但是在前文中提到OSSpinLock
已經(jīng)廢棄了榨为,這里的spinlock_t
在底層是通過(guò)os_unfair_lock
互斥鎖替代了OSSpinLock
自旋鎖:
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
...
}
3.4 pthread_mutex
pthread_mutex
來(lái)自pthread
惨好,是一個(gè)跨平臺(tái)的解決方案,mutex
意為互斥鎖随闺,等待鎖的線(xiàn)程會(huì)處于休眠狀態(tài)日川,它有如下API:
需要先導(dǎo)入 #import <pthread.h>
初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NOMAL);
初始化鎖
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, &attr);
嘗試加鎖
pthread_mutex_trylock(&mutex);
加鎖
pthread_mutex_lock(&mutex);
解鎖
pthread_mutex_unlock(&mutex);
銷(xiāo)毀相關(guān)資源
pthread_mutexattr_destroy(&attr);
pthread_mutex_destroy(&attr);
我們先介紹一下pthread_mutex
的初始化方法:
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * _Nullable __restrict);
-
pthread_mutex_t * __restrict
要進(jìn)行初始化的鎖對(duì)象 -
const pthread_mutexattr_t * _Nullable __restrict
鎖對(duì)象的屬性
由于第二個(gè)參數(shù)是鎖對(duì)象的屬性,所以我們還需要專(zhuān)門(mén)生成屬性對(duì)象矩乐,通過(guò) 定義屬性對(duì)象
-> 初始化屬性對(duì)象
-> 設(shè)置屬性種類(lèi)
3步來(lái)完成龄句,屬性的類(lèi)別有以下幾類(lèi):
#define PTHREAD_MUTEX_NORMAL 0 // 普通互斥鎖
#define PTHREAD_MUTEX_ERRORCHECK 1 // 檢查錯(cuò)誤鎖,不常用
#define PTHREAD_MUTEX_RECURSIVE 2 // 遞歸互斥鎖散罕,下面會(huì)有介紹
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
如果我們給鎖設(shè)定默認(rèn)屬性分歇,那么可以用以下代碼進(jìn)行鎖的初始化,不用再配置屬性信息欧漱,其中參數(shù)NULL表示的就是初始化一個(gè)普通的互斥鎖
pthread_mutex_init(mutex, NULL);
好了职抡,我們先用賣(mài)票示例來(lái)使用一下pthread_mutex
,代碼如下:
#import <pthread.h>
@interface ViewController ()
@property (nonatomic, assign) NSInteger ticketNum;
@property (nonatomic, assign) pthread_mutex_t pthread_lock;
@end
- (void)ticketTest{
// 初始化鎖的屬性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
// 初始化鎖
pthread_mutex_init(&_pthread_lock, &attr);
self.ticketNum = 30;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
// 賣(mài)票操作
- (void)saleTicket{
// 加鎖????
pthread_mutex_lock(&_pthread_lock);
self.ticketNum -= 1;
NSLog(@"還剩%ld張票误甚,currentThread - %@",self.ticketNum,[NSThread currentThread]);
// 解鎖????
pthread_mutex_unlock(&_pthread_lock);
}
-(void)dealloc{
pthread_mutex_destroy(&_pthread_lock);
}
輸出結(jié)果就不貼了缚甩,是沒(méi)有問(wèn)題的,太占地方了窑邦。
3.5 互斥遞歸鎖
請(qǐng)看下面的代碼場(chǎng)景:
-(void)otherTest {
NSLog(@"%s",__func__);
[self otherTest2];
}
-(void)otherTest2 {
NSLog(@"%s",__func__);
}
如果正常調(diào)用 otherTest 這個(gè)方法擅威,結(jié)果如下:
[ViewController otherTest]
[ViewController otherTest2]
如果這兩段代碼都需要保證線(xiàn)程安全,我們通過(guò)加互斥鎖冈钦,來(lái)看下效果:
-(void)otherTest {
//初始化屬性
pthread_mutexattr_init(&_attr);
pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_NORMAL);
//初始化鎖
pthread_mutex_init(&_mutex, &_attr);
pthread_mutex_lock(&_mutex);
NSLog(@"%s",__func__);
[self otherTest2];
pthread_mutex_unlock(&_mutex);
}
-(void)otherTest2 {
pthread_mutex_lock(&_mutex);
NSLog(@"%s",__func__);
pthread_mutex_unlock(&_mutex);
}
輸出結(jié)果:
-[ViewController otherTest]
像上面的代碼一樣郊丛,兩個(gè)方法都加上同一把鎖,可以看到調(diào)用otherTest
方法會(huì)導(dǎo)致線(xiàn)程卡在該方法里面瞧筛,只完成了打印代碼的執(zhí)行宾袜,就不繼續(xù)往下走了,為啥呢驾窟?看下圖:
我們可以從上面代碼中看到庆猫,在otherTest方法中,先加鎖了绅络, [self otherTest2] 在加鎖之后調(diào)用月培,但是otherTest2方法中,此時(shí)還是加鎖的狀態(tài)恩急,所以otherTest2方法一直不執(zhí)行杉畜,由于otherTest方法中,解鎖操作是在otherTest2方法執(zhí)行之后衷恭,所以造成死鎖此叠。
要解決這個(gè)問(wèn)題很簡(jiǎn)單,我們分別給兩個(gè)方法加上不同的鎖對(duì)象就可以解決了随珠。
但是灭袁,如果在開(kāi)發(fā)中如果碰到需要給 遞歸函數(shù) 加鎖猬错,如下面所示:
-(void)otherTest {
pthread_mutex_lock(&_mutex);
NSLog(@"%s",__func__);
//業(yè)務(wù)邏輯
[self otherTest];
pthread_mutex_unlock(&_mutex);
}
這樣就無(wú)法通過(guò)不同的鎖對(duì)象來(lái)加鎖了,只要使用相同的鎖對(duì)象茸歧,就會(huì)出現(xiàn)死鎖倦炒。針對(duì)這種情況,ptherad給我們提供了遞歸鎖來(lái)解決這個(gè)問(wèn)題软瞎。要想使用遞歸鎖逢唤,我們只需要在初始化屬性的時(shí)候,選擇遞歸鎖屬性即可涤浇,其他的使用步驟跟普通互斥鎖沒(méi)有區(qū)別鳖藕,如下:
pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_RECURSIVE);
那么遞歸鎖是如何避免死鎖的呢?對(duì)于同一個(gè)鎖對(duì)象來(lái)說(shuō)只锭,允許重復(fù)的加鎖著恩,重復(fù)的解鎖,因?yàn)閷?duì)于一個(gè)有出口的遞歸函數(shù)來(lái)說(shuō):
函數(shù)的調(diào)用次數(shù) = 函數(shù)的退出次數(shù)
因此加鎖次數(shù)pthread_mutex_lock和解鎖的次數(shù)pthread_mutex_unlock是相等的纹烹,所以遞歸函數(shù)結(jié)束時(shí)页滚,所有的鎖都會(huì)被解開(kāi)召边。
但是遞歸鎖只是針對(duì)在同一個(gè)線(xiàn)程里面可以重復(fù)加鎖和解鎖铺呵。
3.6 互斥條件鎖 pthread_cond_t
pthread_mutex_t mutex; 定義一個(gè)鎖對(duì)象
pthread_mutex_init(&mutex, NULL); 初始化鎖對(duì)象
pthread_cond_t condition; 定義一個(gè)條件對(duì)象
pthread_cond_init(&condition, NULL); 初始化條件對(duì)象
pthread_cond_wait(&condition, &mutex); 等待條件
pthread_cond_signal(&condition); 激活一個(gè)等待該條件的線(xiàn)程
pthread_cond_broadcast(&condition); 激活所有等待條件的線(xiàn)程
pthread_mutex_destroy(&mutex); 銷(xiāo)毀鎖對(duì)象
pthread_cond_destroy(&condition); 銷(xiāo)毀條件對(duì)象
為了解釋互斥鎖條件的作用,我們來(lái)設(shè)計(jì)一種場(chǎng)景案例:
- 在 remove 方法里對(duì)數(shù)組 dataArr 進(jìn)行刪除元素操作
- 在 add 方法里面對(duì)dataArr 進(jìn)行元素添加操作
- 并且要求隧熙,如果 dataArr 的元素個(gè)數(shù)為0片挂,則不能進(jìn)行刪除操作
代碼如下:
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *dataArr;
// 鎖對(duì)象
@property (nonatomic, assign) pthread_mutex_t pthread_lock;
// 條件對(duì)象
@property (nonatomic, assign) pthread_cond_t cond;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化屬性
pthread_mutexattr_t _attr;
pthread_mutexattr_init(&_attr);
pthread_mutexattr_settype(&_attr, PTHREAD_MUTEX_NORMAL);
// 初始化鎖
pthread_mutex_init(&_pthread_lock, &_attr);
// 初始化條件
pthread_cond_init(&_cond, NULL);
pthread_mutexattr_destroy(&_attr);
// 初始化數(shù)組
self.dataArr = [NSMutableArray array];
[self dataArrTest];
}
- (void)dataArrTest{
// 開(kāi)啟remove 操作,此時(shí)dataArr為空
NSThread *removeThread = [[NSThread alloc] initWithTarget:self selector:@selector(remove) object:nil];
[removeThread setName:@"RemoveThread"];
[removeThread start];
// 開(kāi)啟add操作
NSThread *addThread = [[NSThread alloc] initWithTarget:self selector:@selector(add) object:nil];
[addThread setName:@"AddThread"];
[addThread start];
}
// 移除元素
- (void)remove{
// 加鎖
pthread_mutex_lock(&_pthread_lock);
NSLog(@"加鎖成功贞盯,線(xiàn)程:%@音念,remove操作開(kāi)始",[NSThread currentThread]);
if (!self.dataArr.count) {
// 進(jìn)行條件等待
NSLog(@"dataArr沒(méi)有元素,開(kāi)始等待");
pthread_cond_wait(&_cond, &_pthread_lock);
NSLog(@"接收到條件更新信號(hào)躏敢,dataArr有了元素闷愤,繼續(xù)remove操作");
}
[self.dataArr removeLastObject];
NSLog(@"remove成功,dataArr剩余元素個(gè)數(shù):%ld",self.dataArr.count);
// 解鎖
pthread_mutex_unlock(&_pthread_lock);
NSLog(@"remove 解鎖成功件余,線(xiàn)程:%@讥脐,線(xiàn)程結(jié)束\n",[NSThread currentThread]);
}
// 添加元素
- (void)add{
// 加鎖
pthread_mutex_lock(&_pthread_lock);
NSLog(@"加鎖成功,線(xiàn)程:%@啼器,add操作開(kāi)始",[NSThread currentThread]);
sleep(2);
[self.dataArr addObject:@"111"];
NSLog(@"add成功旬渠,dataArr剩余元素個(gè)數(shù):%ld,發(fā)送條件信號(hào)",self.dataArr.count);
// 發(fā)送條件信號(hào)
pthread_cond_signal(&_cond);
// 解鎖
pthread_mutex_unlock(&_pthread_lock);
NSLog(@"add 解鎖成功端壳,線(xiàn)程:%@告丢,線(xiàn)程結(jié)束",[NSThread currentThread]);
}
-(void)dealloc{
pthread_mutex_destroy(&_pthread_lock);
}
@end
輸出結(jié)果:
2019-10-14 15:14:52.929491+0800 tianran[2004:431723] 加鎖成功,線(xiàn)程:<NSThread: 0x283f8cd40>{number = 6, name = RemoveThread}损谦,remove操作開(kāi)始
2019-10-14 15:14:52.929557+0800 tianran[2004:431723] dataArr沒(méi)有元素岖免,開(kāi)始等待
2019-10-14 15:14:52.929641+0800 tianran[2004:431724] 加鎖成功岳颇,線(xiàn)程:<NSThread: 0x283f8c9c0>{number = 7, name = AddThread},add操作開(kāi)始
2019-10-14 15:14:54.935014+0800 tianran[2004:431724] add成功觅捆,dataArr剩余元素個(gè)數(shù):1赦役,發(fā)送條件信號(hào)
2019-10-14 15:14:54.935672+0800 tianran[2004:431723] 接收到條件更新信號(hào),dataArr有了元素栅炒,繼續(xù)remove操作
2019-10-14 15:14:54.935790+0800 tianran[2004:431724] add 解鎖成功掂摔,線(xiàn)程:<NSThread: 0x283f8c9c0>{number = 7, name = AddThread},線(xiàn)程結(jié)束
2019-10-14 15:14:54.936061+0800 tianran[2004:431723] remove成功赢赊,dataArr剩余元素個(gè)數(shù):0
2019-10-14 15:14:54.936415+0800 tianran[2004:431723] remove 解鎖成功乙漓,線(xiàn)程:<NSThread: 0x283f8cd40>{number = 6, name = RemoveThread},線(xiàn)程結(jié)束
從案例以及運(yùn)行結(jié)果分析释移,互斥鎖的條件pthread_cond_t可以在線(xiàn)程加鎖之后叭披,如果條件不達(dá)標(biāo),暫停線(xiàn)程玩讳,等到條件符合標(biāo)準(zhǔn)涩蜘,繼續(xù)執(zhí)行線(xiàn)程,請(qǐng)看下圖:
總結(jié)一下pthread_cond_t的作用:
- 首先在A(yíng)線(xiàn)程內(nèi)碰到業(yè)務(wù)邏輯無(wú)法往下執(zhí)行的時(shí)候熏纯,調(diào)用pthread_cond_wait(),這句代碼首先會(huì)解鎖當(dāng)前線(xiàn)程同诫,然后休眠當(dāng)前線(xiàn)程以等待條件信號(hào)
- 此時(shí),鎖已經(jīng)解開(kāi)樟澜,那么值錢(qián)等待鎖的B線(xiàn)程可以成功加鎖误窖,執(zhí)行它后面的邏輯,由于B線(xiàn)程內(nèi)的某些操作完成后可以出發(fā)A的運(yùn)行條件秩贰,此時(shí)從B線(xiàn)程通過(guò)pthread_cond_signal(&_cond)向外發(fā)出條件信號(hào)
- A線(xiàn)程的收到了條件信號(hào)就會(huì)被pthread_cond_t喚醒霹俺,一旦B線(xiàn)程解鎖之后,pthread_cond_t會(huì)在A(yíng)線(xiàn)程內(nèi)重新加鎖毒费,繼續(xù)A線(xiàn)程的后續(xù)操作丙唧,并最終解鎖。從前到后觅玻,有三次加鎖想际,三次解鎖
- 通過(guò)pthread_cond_t就實(shí)現(xiàn)了一種線(xiàn)程與線(xiàn)程之間的依賴(lài)關(guān)系,實(shí)際開(kāi)發(fā)中我們會(huì)有不少場(chǎng)景需要用到這種跨線(xiàn)程依賴(lài)關(guān)系
3.7 NSLock NSRecursiveLock NSCondition
上面我們了解的 mutex普通鎖
串塑,mutex遞歸鎖
沼琉,mutex條件鎖
,都是基于C語(yǔ)言的API桩匪,蘋(píng)果在此基礎(chǔ)上打瘪,進(jìn)行了一層面向?qū)ο蟮姆庋b:
-
NSLock
封裝了pthread_mutex_t
(attr = 普通) -
NSRecursiveLock
封裝了pthread_mutex_t
(attr = 遞歸) -
NSCondition
封裝了pthread_mutex_t + pthread_cond_t
由于底層是pthread_mutex
,這里不再通過(guò)代碼案例演示,下面列舉一下相關(guān)API使用方法:
//普通鎖
NSLock *lock = [[NSLock alloc] init];
[lock lock];
[lock unlock];
//遞歸鎖
NSRecursiveLock *rec_lock = [[NSRecursiveLock alloc]
[rec_lock lock];
[rec_lock unlock];init];
//條件鎖
NSCondition *condition = [[NSCondition alloc] init];
[self.condition lock];
[self.condition wait];
[self.condition signal];
[self.condition unlock];
3.8 NSConditionLock
蘋(píng)果基于NSCondition
又進(jìn)一步封裝了NSConditionLock
闺骚,該鎖允許我們?cè)阪i中設(shè)定條件具體條件值彩扔,有了這個(gè)功能,我們可以更加方便的管理多條線(xiàn)程的依賴(lài)關(guān)系和前后執(zhí)行順序僻爽,首先看一下相關(guān)API:
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
特色功能:
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
接下來(lái)通過(guò)案例來(lái)說(shuō)明它的功能:
@interface ViewController ()
@property (nonatomic, strong) NSConditionLock *conditionLock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.conditionLock = [[NSConditionLock alloc] init];
[self dataArrTest];
}
- (void)dataArrTest{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self.conditionLock lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
});
dispatch_async(queue, ^{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
sleep(1);
[self.conditionLock unlockWithCondition:3];
});
dispatch_async(queue, ^{
[self.conditionLock lockWhenCondition:3];
NSLog(@"__three");
sleep(1);
[self.conditionLock unlock];
});
}
@end
代碼輸出:
__one __two __three
全局并發(fā)隊(duì)列虫碉,異步操作,這里就是使用NSConditionLock達(dá)到了線(xiàn)程依賴(lài)的功能胸梆,使用NSOperation也可以敦捧,我們用圖說(shuō)明一下:
3.9 dispatch_semaphore
GCD提供了dispatch_semaphore方案來(lái)處理多線(xiàn)程同步問(wèn)題。
3.10 @synchronized
這個(gè)使用很簡(jiǎn)單碰镜,也比較常見(jiàn):
@synchronized (lockObj) {
/*
加鎖代碼(臨界區(qū))
*/
}
但是它是所有線(xiàn)程同步方案里面性能最差的兢卵。
開(kāi)啟匯編調(diào)試,發(fā)現(xiàn)@synchronized
在執(zhí)行過(guò)程中绪颖,會(huì)走底層的 objc_sync_enter
和 objc_sync_exit
方法:
也可以通過(guò) clang秽荤,查看底層編譯代碼:
通過(guò)對(duì)objc_sync_enter
方法進(jìn)行符號(hào)斷點(diǎn),查看底層所在的源碼庫(kù)柠横,通過(guò)斷點(diǎn)發(fā)現(xiàn)在objc
源碼中窃款,進(jìn)入objc_sync_enter
的源碼實(shí)現(xiàn):
int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//傳入不為nil
SyncData* data = id2data(obj, ACQUIRE);//重點(diǎn)
ASSERT(data);
data->mutex.lock();//加鎖
} else {//傳入nil
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}
return result;
}
主要操作是判斷 obj 有值,然后通過(guò)id2data
獲取了相應(yīng)的 SyncData
牍氛,然后進(jìn)行了加鎖操作晨继。
我們?cè)倏聪?code>objc_sync_exit源碼實(shí)現(xiàn):
// End synchronizing on 'obj'. 結(jié)束對(duì)“ obj”的同步
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {//obj不為nil
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();//解鎖
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {//obj為nil時(shí),什么也不做
// @synchronized(nil) does nothing
}
return result;
}
這個(gè)方法在獲取了對(duì)應(yīng)的 SyncData
之后糜俗,進(jìn)行了解鎖操作踱稍。
通過(guò)上面兩個(gè)實(shí)現(xiàn)邏輯發(fā)現(xiàn)曲饱,在 obj 存在時(shí)悠抹,都會(huì)通過(guò)id2data
方法,獲取SyncData
扩淀。
進(jìn)入 SyncData
楔敌,是一個(gè)結(jié)構(gòu)體:
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
其內(nèi)部有一個(gè)結(jié)構(gòu)體的指針struct SyncData* nextData
,類(lèi)似于鏈表
結(jié)構(gòu)驻谆,有 next
指向卵凑,并且封裝了recursive_mutex_t mutex
,確認(rèn)@synchronized
是一個(gè)遞歸互斥鎖
胜臊。
進(jìn)入SyncCache
定義勺卢,也是一個(gè)結(jié)構(gòu)體,用于存儲(chǔ)線(xiàn)程
象对,其中 list[0]
表示當(dāng)前線(xiàn)程的鏈表 data
黑忱,主要用于存儲(chǔ) SyncData 和 lockCount
:
typedef struct {
SyncData *data;
unsigned int lockCount; // number of times THIS THREAD locked this block
} SyncCacheItem;
typedef struct SyncCache {
unsigned int allocated;
unsigned int used;
SyncCacheItem list[0];
} SyncCache;
我們現(xiàn)在來(lái)看下主要的id2data
方法,這個(gè)是加鎖和解鎖都會(huì)調(diào)用的方法:
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS //tls(Thread Local Storage,本地局部的線(xiàn)程緩存)
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
//通過(guò)KVC方式對(duì)線(xiàn)程進(jìn)行獲取 線(xiàn)程綁定的data
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
//如果線(xiàn)程緩存中有data甫煞,執(zhí)行if流程
if (data) {
fastCacheOccupied = YES;
//如果在線(xiàn)程空間找到了data
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
//通過(guò)KVC獲取lockCount菇曲,lockCount用來(lái)記錄 被鎖了幾次,即 該鎖可嵌套
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
//objc_sync_enter走這里抚吠,傳入的是ACQUIRE -- 獲取
lockCount++;//通過(guò)lockCount判斷被鎖了幾次常潮,即表示 可重入(遞歸鎖如果可重入,會(huì)死鎖)
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);//設(shè)置
break;
}
case RELEASE:
//objc_sync_exit走這里楷力,傳入的why是RELEASE -- 釋放
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);//判斷緩存中是否有該線(xiàn)程
//如果cache中有喊式,方式與線(xiàn)程緩存一致
if (cache) {
unsigned int I;
for (i = 0; i < cache->used; i++) {//遍歷總表
SyncCacheItem *item = &cache->list[I];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE://加鎖
item->lockCount++;
break;
case RELEASE://解鎖
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache 從cache中清除使用標(biāo)記
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
//第一次進(jìn)來(lái),所有緩存都找不到
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {//cache中已經(jīng)找到
if ( p->object == object ) {//如果不等于空萧朝,且與object相似
result = p;//賦值
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);//對(duì)threadCount進(jìn)行++
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object 沒(méi)有與當(dāng)前對(duì)象關(guān)聯(lián)的SyncData
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it 第一次進(jìn)來(lái)垃帅,沒(méi)有找到
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));//創(chuàng)建賦值
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) { //判斷是否支持棧存緩存,支持則通過(guò)KVC形式賦值 存入tls
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);//lockCount = 1
} else
#endif
{
// Save in thread cache 緩存中存一份
if (!cache) cache = fetch_cache(YES);//第一次存儲(chǔ)時(shí)剪勿,對(duì)線(xiàn)程進(jìn)行了綁定
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
第一步:首先在 tls 即線(xiàn)程緩存中查找贸诚。
a.在 tsl_get_direct
方法中以線(xiàn)程為key
,通過(guò) kvc
的方式獲取與之綁定的 SyncData
厕吉,即線(xiàn)程 data
酱固,其中的tls()
,表示本地局部的線(xiàn)程緩存头朱;
b.判斷獲取的data
是否存在运悲,以及判斷data
中是否能找到對(duì)應(yīng)的 object
;
c.如果都找到了,在tsl_get_direct
方法中以 KVC
的方式獲取 lockCount
项钮,用來(lái)記錄對(duì)象被鎖了幾次(即鎖的嵌套次數(shù))
d.如果data
中的threadCount
小于等于0班眯,或者lockCount
小于等于0時(shí)殴穴,則直接崩潰
e.通過(guò)傳入的why
壳鹤,判斷操作類(lèi)型:
e.1 如果是ACQUIRE
蝇棉,表示加鎖刃滓,則進(jìn)行lockCount++
人柿,并保存到tls緩存
e.2 如果是RELEASE
何乎,表示釋放子漩,則進(jìn)行lockCount--
幼衰,并保存到tls
緩存阿弃。如果lockCount
等于 0诊霹,從tls
中移除線(xiàn)程data
e.3 如果是CHECK
,則什么也不做
第二步:如果 tls
中沒(méi)有渣淳,則在cache緩存
中查找
a.通過(guò) fetch_cache
方法查找 cache
緩存中是否有線(xiàn)程
b.如果有脾还,則遍歷 cache
總表,讀取線(xiàn)程對(duì)應(yīng)的 SyncCacheItem
c. 從SyncCacheItem
中取出data
入愧,然后后續(xù)步驟與tls
的匹配是一致的
第三步:如果 cache
中也沒(méi)有鄙漏,即第一次進(jìn)來(lái)赛蔫,則創(chuàng)建 SyncData
,并存儲(chǔ)到相應(yīng)緩存中:
a.如果在cache
中找到線(xiàn)程泥张,且與object
相等呵恢,則進(jìn)行賦值、以及threadCount++
b.如果在cache
中沒(méi)有找到媚创,則threadCount
等于1
針對(duì)tls
和 cache
緩存渗钉,底層的表結(jié)構(gòu)如下:
- 哈希表結(jié)構(gòu)中通過(guò)
SyncList
結(jié)構(gòu)來(lái)組裝多線(xiàn)程的情況 -
SyncData
通過(guò)鏈表的形式組裝當(dāng)前可重入的情況 - 下層通過(guò)
tls
線(xiàn)程緩存、cache
緩存來(lái)進(jìn)行處理 - 底層主要有兩個(gè)東西:
lockCount
钞钙、threadCount
鳄橘,解決了遞歸互斥鎖,解決了嵌套可重入
3.11 總結(jié)
我們對(duì)iOS的各種線(xiàn)程同步方案體驗(yàn)了一下:
-
OSSpinLock
由于其不休眠特性芒炼,所以它的效率是非常高的瘫怜,但是由于安全問(wèn)題,蘋(píng)果建議我們使用os_unfair_lock
取而代之本刽,并且效率還要高于前者鲸湃。 -
pthread_mutex
是一種跨平臺(tái)的解決方案,性能也不錯(cuò)子寓。當(dāng)然還有蘋(píng)果的GCD
解決方案暗挑,也是挺不錯(cuò)的。 - 對(duì)于
NS
開(kāi)頭的那些OC
下的解決方案斜友,雖然本質(zhì)也還是基于pthread_mutex
的封裝炸裆,但是由于多了一些面向?qū)ο蟮牟僮鏖_(kāi)銷(xiāo),效率不免要下降鲜屏。 - 性能最差的是
@synchronized
方案烹看,雖然它的使用是最簡(jiǎn)單的,但因?yàn)樗牡讓臃庋b了過(guò)于復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
洛史,導(dǎo)致了性能低下惯殊。
使用推薦如下: - os_unfair_lock(推薦??????????)
- OSSpinLock(不安全????)
- dispatch_semaphore(推薦??????????)
- pthread_mutex(推薦????????)
- dispatch_queue(DISPATCH_QUEUE_SERIAL)(推薦??????)
- NSLock(??????)
- NSCondition(??????)
- pthread_mutex(recursive)(????)
- NSRecursiveLock(????)
- NSConditionLock(????)
- @synchronized(最不推薦)
4. 自旋鎖和互斥鎖的對(duì)比
4.1 什么情況下選擇自旋鎖更好?
自旋鎖的特點(diǎn):效率高虹菲、安全性不足靠胜、占用CPU資源大掉瞳,因此選擇自旋鎖依據(jù)原則如下:
- 預(yù)計(jì)線(xiàn)程等待鎖的時(shí)間很短
- 加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用毕源,但是競(jìng)爭(zhēng)的情況發(fā)生概率很小,對(duì)安全性要求不高
-
CPU
資源不緊張 - 多核處理器
4.2 什么情況下選擇互斥鎖更好陕习?
互斥鎖特點(diǎn):安全性突出霎褐、占用CPU資源小,休眠/喚醒過(guò)程要消耗CPU資源该镣,因此選擇互斥鎖依據(jù)原則如下:
- 預(yù)計(jì)線(xiàn)程等待鎖的時(shí)間比較長(zhǎng)
- 單核處理器
- 臨界區(qū)有
IO操作
- 臨界區(qū)代碼復(fù)雜或者循環(huán)量大
- 臨界區(qū)的競(jìng)爭(zhēng)非常激烈冻璃,對(duì)安全性要求高
4.3 為什么iOS中幾乎不用atomic
?
atomic
是用于保證屬性的setter、getter
方法的原子性操作的省艳,本質(zhì)就是在getter和setter
內(nèi)部增加線(xiàn)程同步的鎖娘纷,用的鎖實(shí)質(zhì)上也是os_unfair_lock
。
但是atomic
只是保證讀寫(xiě)操作的線(xiàn)程同步跋炕,對(duì)于可變數(shù)組赖晶、可變字典來(lái)說(shuō),如果你使用atomic
修飾辐烂,在添加元素的時(shí)候遏插,并不是線(xiàn)程安全的:
NSMutableArray *arr = self.dataArr;//getter方法是安全的
for (int i = 0; i<5; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[arr addObject:@"1"];//這里會(huì)有多線(xiàn)程操作_dataArr,atomic無(wú)法保證這里的線(xiàn)程同步
});
}
所以說(shuō)atomic
并不能完全保證多線(xiàn)程安全問(wèn)題。
我們幾乎不會(huì)用到atomic
纠修,因?yàn)?code>property在iOS
代碼中調(diào)用的太頻繁了胳嘲,會(huì)導(dǎo)致鎖的過(guò)度使用,消耗CPU
資源扣草,所以我們只需要針對(duì)具體會(huì)出現(xiàn)多線(xiàn)程隱患的地方加鎖就行了了牛,需要加鎖的時(shí)候再去加。
4.4 多線(xiàn)程讀寫(xiě)安全
在上面存取錢(qián)的示例中辰妙,存白魂、和取其實(shí)就是對(duì)共享資源的讀和寫(xiě)
,假如我們有如下兩個(gè)操作分別只包含讀操作和寫(xiě)操作:
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
其實(shí)讀操作的目的上岗,只是取出數(shù)據(jù)福荸,并不會(huì)修改數(shù)據(jù),所以多線(xiàn)程同時(shí)進(jìn)行讀操作
是沒(méi)問(wèn)題的肴掷,不需要考慮線(xiàn)程同步的問(wèn)題敬锐,寫(xiě)操作
是導(dǎo)致多線(xiàn)程安全問(wèn)題的根本因素。所以為了讀寫(xiě)安全呆瞻,解決方案其實(shí)就是多讀單寫(xiě):
- 要求1: 同一時(shí)間台夺,只能有1個(gè)線(xiàn)程進(jìn)行寫(xiě)的操作
- 要求2: 同一時(shí)間,允許有多個(gè)線(xiàn)程進(jìn)行讀的操作
- 要求3: 同一時(shí)間痴脾,不允許既讀又寫(xiě)颤介,就是說(shuō)讀操作和寫(xiě)操作之間是互斥關(guān)系
iOS有兩種方案可以實(shí)現(xiàn)上述的讀寫(xiě)安全需求
-
pthread_rwlock:
讀寫(xiě)鎖 -
dispatch_barrier_async:
異步柵欄調(diào)用
-
pthread_rwlock
使用pthread_rwlock
,等待鎖的線(xiàn)程會(huì)進(jìn)入休眠赞赖,API如下:
//初始化鎖
pthread_rwlock_t lock;
pthread_rwlock_init(&lock, NULL);
//讀操作加鎖
pthread_rwlock_rdlock(&lock);
//讀操作嘗試加鎖
pthread_rwlock_tryrdlock(&lock);
//寫(xiě)操作加鎖
pthread_rwlock_wrlock(&lock);
//寫(xiě)操作嘗試加鎖
pthread_rwlock_trywrlock(&lock);
//解鎖
pthread_rwlock_unlock(&lock);
//銷(xiāo)毀鎖
pthread_rwlock_destroy(&lock);
-
dispatch_barrier_async
使用dispatch_barrier_async
有一個(gè)注意點(diǎn)滚朵,這個(gè)函數(shù)接受的并發(fā)隊(duì)列參數(shù)必須是你自己手動(dòng)創(chuàng)建的(dispatch_queue_create)
,如果接受的是一個(gè)串行隊(duì)列或者是一個(gè)全局并發(fā)隊(duì)列前域,那么這個(gè)函數(shù)的效果等同于dispatch_async
函數(shù)辕近。
具體介紹,可以看另外一篇文章匿垄,里面有對(duì)于GCD
常用方法的介紹:
http://www.reibang.com/p/3f9910293401