本文主要介紹了 iOS的多線程方案, 多線程安全方案, 多讀單寫方案.
篇幅稍長,還請(qǐng)耐心看完.
進(jìn)程
理論上,每個(gè)iOS App都是一個(gè)進(jìn)程, 有自己獨(dú)立的虛擬空間來存儲(chǔ)自己的運(yùn)行數(shù)據(jù).
線程
每個(gè)進(jìn)程中有多個(gè)線程,這些線程共享進(jìn)程的全局變量和堆數(shù)據(jù). 多條線程可以在一個(gè)進(jìn)程中并發(fā)執(zhí)行.達(dá)到同一時(shí)間完成多個(gè)任務(wù)的效果. 其實(shí)在單處理器中,所謂的并發(fā),是操作系統(tǒng)不斷的在線程間來回切換達(dá)到的一個(gè)偽并發(fā)效果.
多線程的作用
- 避免線程堵塞,有些需要大量時(shí)間執(zhí)行的任務(wù),如果放在主線程同步執(zhí)行,則會(huì)造成卡頓
- 將復(fù)雜任務(wù)拆分, 如UITableViewCell加載圖片時(shí),可以開辟新線程去處理圖片數(shù)據(jù),最終再交由主線程實(shí)現(xiàn)
- 多任務(wù)并行
iOS中的多線程方案
- pthread: C語言,使用難度大.需要由使用者管理生命周期. 基本沒人用
- NSThread: OC 面向?qū)ο?需要依賴Runloop保活,需要由使用者管理生命周期,使用較少
- GCD: C語言. 由系統(tǒng)本身管理生命周期 是目前較主流的多線程方案
- NSOperation: OC語言 基于GCD的封裝,更加面向?qū)ο? 由系統(tǒng)本身管理聲明周期. 也比較多人使用
隊(duì)列
隊(duì)列中裝載著多個(gè)線程,根據(jù)隊(duì)列的不同屬性安排線程的任務(wù)調(diào)度,隊(duì)列可分為串行隊(duì)列和并發(fā)隊(duì)列
- 串行隊(duì)列:隊(duì)列中的任務(wù)一個(gè)一個(gè)連成串執(zhí)行
- 并發(fā)隊(duì)列:隊(duì)列可以同時(shí)執(zhí)行任務(wù)
同步
所有任務(wù)在同一個(gè)線程一個(gè)接一個(gè)執(zhí)行
B任務(wù)必須等待A任務(wù)執(zhí)行完成才可以進(jìn)行. 如果A任務(wù)耗時(shí)太長, 則B任務(wù)也會(huì)一直等到A任務(wù)進(jìn)行完成.
iOS場(chǎng)景: 加載網(wǎng)絡(luò)數(shù)據(jù)時(shí),使用加載指示器擋住當(dāng)前view, 等到加載完成后指示器消失,頁面展示數(shù)據(jù). 如果此時(shí)加載任務(wù)遲遲未完成.則界面一直卡在加載界面.
有可能會(huì)引起死鎖
異步
在新的線程中執(zhí)行任務(wù).
下面看看同步異步在不同線程的表現(xiàn)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"main thread %@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main dispatch_async_thred:%@",[NSThread currentThread]);
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"global dispatch_sync_thred:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"global dispatch_async_thred:%@",[NSThread currentThread]);
});
dispatch_queue_t squeue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(squeue, ^{
NSLog(@"squeue dispatch_sync_thred:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
NSLog(@"squeue dispatch_async_thred:%@",[NSThread currentThread]);
});
dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(cqueue, ^{
NSLog(@"cqueue dispatch_sync_thred:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
NSLog(@"cqueue dispatch_async_thred:%@",[NSThread currentThread]);
});
}
main thread <NSThread: 0x600003a0c200>{number = 1, name = main}
global dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
global dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
squeue dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
cqueue dispatch_sync_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
squeue dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
cqueue dispatch_async_thred:<NSThread: 0x600003a4d980>{number = 2, name = (null)}
main dispatch_async_thred:<NSThread: 0x600003a0c200>{number = 1, name = main}
從結(jié)果可以看到.
- 同步:都沒有開啟新的線程
- 異步:全局隊(duì)列,串行隊(duì)列,并發(fā)隊(duì)列中開辟了新線程. 主隊(duì)列不開辟新線程
主隊(duì)列異步執(zhí)行任務(wù)情況
NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 6:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 7:%@",[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"main async 10:%@",[NSThread currentThread]);
});
1<NSThread: 0x600001564140>{number = 1, name = main}
3<NSThread: 0x600001564140>{number = 1, name = main}
5<NSThread: 0x600001564140>{number = 1, name = main}
9<NSThread: 0x600001564140>{number = 1, name = main}
main async 2:<NSThread: 0x600001564140>{number = 1, name = main}
main async 4:<NSThread: 0x600001564140>{number = 1, name = main}
main async 6:<NSThread: 0x600001564140>{number = 1, name = main}
main async 7:<NSThread: 0x600001564140>{number = 1, name = main}
main async 8:<NSThread: 0x600001564140>{number = 1, name = main}
main async 10:<NSThread: 0x600001564140>{number = 1, name = main}
從結(jié)果可以得出. 在主隊(duì)列中運(yùn)行異步任務(wù), 會(huì)等待viewdidload中的任務(wù)執(zhí)行完成后,再串行的執(zhí)行任務(wù).那么根據(jù)上述的結(jié)果.拋出一個(gè)問題.下屬代碼,在主隊(duì)列執(zhí)行同步任務(wù).那么會(huì)有什么后果.
NSLog(@"1%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"main sync 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
答案就是:死鎖. 因?yàn)橥饺蝿?wù)需要等待viewdidload執(zhí)行完畢,而viewdidload又要等待同步任務(wù)出隊(duì).大家一起等.大家都不出隊(duì).那就大家抱著一起卡死唄.
全局隊(duì)列異步執(zhí)行任務(wù)情況
dispatch_queue_t gQueue = dispatch_get_global_queue(0, 0);
NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
NSLog(@"global async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
NSLog(@"global async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
NSLog(@"global async 6:%@",[NSThread currentThread]);
});
dispatch_async(gQueue, ^{
NSLog(@"global async 7:%@",[NSThread currentThread]);
});
dispatch_async(gQueue, ^{
NSLog(@"global async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(gQueue, ^{
NSLog(@"global async 10:%@",[NSThread currentThread]);
});
1<NSThread: 0x6000003f0080>{number = 1, name = main}
3<NSThread: 0x6000003f0080>{number = 1, name = main}
global async 2:<NSThread: 0x6000003b1840>{number = 6, name = (null)}
5<NSThread: 0x6000003f0080>{number = 1, name = main}
9<NSThread: 0x6000003f0080>{number = 1, name = main}
global async 4:<NSThread: 0x6000003b2180>{number = 7, name = (null)}
global async 6:<NSThread: 0x6000003b1840>{number = 6, name = (null)}
global async 7:<NSThread: 0x6000003f3380>{number = 5, name = (null)}
global async 8:<NSThread: 0x6000003b2180>{number = 7, name = (null)}
global async 10:<NSThread: 0x6000003ec9c0>{number = 4, name = (null)}
我們上面已經(jīng)得出結(jié)論.在全局隊(duì)列執(zhí)行異步任務(wù),是會(huì)開辟新的線程.所以很明顯.他并不需要等到viewdidload執(zhí)行完畢就可以執(zhí)行任務(wù).
自建串行隊(duì)列異步執(zhí)行任務(wù)情況
dispatch_queue_t squeue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
NSLog(@"squeue async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
NSLog(@"squeue async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
NSLog(@"squeue async 6:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
NSLog(@"squeue async 7:%@",[NSThread currentThread]);
});
dispatch_async(squeue, ^{
NSLog(@"squeue async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(squeue, ^{
NSLog(@"squeue async 10:%@",[NSThread currentThread]);
});
1<NSThread: 0x60000215c600>{number = 1, name = main}
3<NSThread: 0x60000215c600>{number = 1, name = main}
squeue async 2:<NSThread: 0x600002108140>{number = 6, name = (null)}
5<NSThread: 0x60000215c600>{number = 1, name = main}
9<NSThread: 0x60000215c600>{number = 1, name = main}
squeue async 4:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 6:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 7:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 8:<NSThread: 0x600002108140>{number = 6, name = (null)}
squeue async 10:<NSThread: 0x600002108140>{number = 6, name = (null)}
結(jié)果與全局隊(duì)列等同,但只創(chuàng)建了一條線程
自建并發(fā)隊(duì)列異步執(zhí)行任務(wù)情況
dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 2:%@",[NSThread currentThread]);
});
NSLog(@"3%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 4:%@",[NSThread currentThread]);
});
NSLog(@"5%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 6:%@",[NSThread currentThread]);
});
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 7:%@",[NSThread currentThread]);
});
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 8:%@",[NSThread currentThread]);
});
NSLog(@"9%@",[NSThread currentThread]);
dispatch_async(cqueue, ^{
NSLog(@"cqueue async 10:%@",[NSThread currentThread]);
});
1<NSThread: 0x6000028a03c0>{number = 1, name = main}
3<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 2:<NSThread: 0x60000289d1c0>{number = 3, name = (null)}
5<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 4:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
cqueue async 6:<NSThread: 0x60000289d1c0>{number = 3, name = (null)}
cqueue async 7:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
cqueue async 8:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
9<NSThread: 0x6000028a03c0>{number = 1, name = main}
cqueue async 10:<NSThread: 0x6000028f8800>{number = 7, name = (null)}
結(jié)果與全局隊(duì)列相同,也創(chuàng)建了多條線程
global_queue
從上述結(jié)果對(duì)比,global_queue是一條并發(fā)隊(duì)列
main_queue
main_queue是一條串行隊(duì)列
死鎖
在往主隊(duì)列添加同步任務(wù)時(shí),會(huì)造成死鎖.其實(shí)不只是主隊(duì)列,在串行隊(duì)列同步任務(wù)中添加同步任務(wù),也會(huì)引起死鎖.一下代碼同樣會(huì)引起奔潰.而在并發(fā)隊(duì)列中則不會(huì)產(chǎn)生這種情況.
dispatch_queue_t cqueue = dispatch_queue_create("QUEUE1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(cqueue, ^{
NSLog(@"cqueue async 2:%@",[NSThread currentThread]);
dispatch_sync(cqueue, ^{
NSLog(@"cqueue sync 3:%@",[NSThread currentThread]);
});
});
線程安全
多線程在帶來便利的同時(shí),也會(huì)帶來隱患.當(dāng)多個(gè)線程對(duì)同一個(gè)變量進(jìn)行寫操作時(shí),可能會(huì)造成結(jié)果不同.賣票問題就是很典型的多線程導(dǎo)致的隱患.
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 40; i++) {
[self saleTickets];
}
});
}
- (void)saleTickets {
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
}
從最終結(jié)果可以看出.最終的余票并不為0.
說明有多條線程在同時(shí)操作一塊內(nèi)存
例如:多個(gè)線程訪問tickets時(shí)值為98,進(jìn)行減1. 所以造成了結(jié)果的不同.
解決方法1.加鎖
iOS中的鎖
- OSSpinLock(自旋鎖)
- os_unfairLock
- pthread_mutex
- dispatch_semaphor
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditonLock
- @synchronized
自旋鎖
當(dāng)線程檢測(cè)到自旋鎖上鎖時(shí),會(huì)進(jìn)行忙等,直到鎖被釋放,才會(huì)繼續(xù)執(zhí)行任務(wù).類似于while(lock){}
互斥鎖
當(dāng)線程檢測(cè)到上鎖時(shí),該線程會(huì)進(jìn)行休眠,等到其他線程解鎖,才會(huì)喚起該線程.
OSSpinLock
#import <libkern/OSAtomic.h>
- (void)viewDidLoad {
[super viewDidLoad];
self.lock = OS_SPINLOCK_INIT;
[self beginSaleTicket];
}
- (void)saleTickets {
OSSpinLockLock(&_lock);
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
OSSpinLockUnlock(&_lock);
}
- (void)beginSaleTicket {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 40; i++) {
[self saleTickets];
}
});
}
最終執(zhí)行結(jié)果,發(fā)現(xiàn)最終余票為0. 也就是說,線程安全了
OSSpinLock為什么被棄用
原因1:死鎖
#import <os/lock.h>
- (void)viewDidLoad {
[super viewDidLoad];
self.lock = OS_SPINLOCK_INIT;
[self deadLock];
NSLog(@"test");
}
- (void)deadLock {
OSSpinLockLock(&_lock);
[self lockAgain];
NSLog(@"等待lockAgain執(zhí)行完成");
OSSpinLockUnlock(&_lock);
}
- (void)lockAgain {
OSSpinLockLock(&_lock);
NSLog(@"加鎖");
OSSpinLockUnlock(&_lock);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touch");
}
上述代碼的結(jié)果就是, 什么都不輸出, touchesBegan也無法響應(yīng).為什么呢?分析一下
自旋鎖中, deadLock方法拿到鎖,上鎖后調(diào)用lockAgain,此時(shí)檢測(cè)到_lock上鎖,lockAgain會(huì)進(jìn)入忙等. deadLock被堵住,無法拿到鎖進(jìn)行解鎖.所以線程卡死. 當(dāng)然, 互斥鎖也會(huì)有這種問題發(fā)生.
原因2:優(yōu)先級(jí)反轉(zhuǎn)
線程運(yùn)行時(shí)搶占式的, 當(dāng)高優(yōu)先級(jí)的線程輪轉(zhuǎn)時(shí),會(huì)搶占低優(yōu)先級(jí)任務(wù)的CPU控制器 .
假設(shè)有 A,B兩條線程, 優(yōu)先級(jí) : A > B
B先于A持有鎖并上鎖, 此時(shí)A輪轉(zhuǎn), 搶占B的CPU控制權(quán), B未對(duì)鎖就行解鎖操作, A任務(wù)檢測(cè)到上了鎖, 忙等, 最終掛起線程, 繼續(xù)回到B操作. 只有兩條線程可能問題不大. 但是如果有多個(gè)線程不斷輪轉(zhuǎn), 就會(huì)出現(xiàn)優(yōu)先級(jí)最高的任務(wù)A遲遲無法完成, 最終影響了程序性能
os_unfair_lock
iOS10之后, OC推出的用來替代 OSSpinLock 的鎖,
- (void)viewDidLoad {
[super viewDidLoad];
self.lock = OS_UNFAIR_LOCK_INIT;
[self beginSaleTicket];
}
- (void)saleTickets {
os_unfair_lock_lock(&_lock);
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
os_unfair_lock_unlock(&_lock);
}
使用方法也很簡單, iOS內(nèi)部沒有標(biāo)明os_unfair_lock是自旋鎖還是互斥鎖, 但是通過從匯編看到的結(jié)果, 檢測(cè)到加鎖的線程走到某一步之后, 是直接休眠的. 根據(jù)定義看, os_unfair_lock是一個(gè)互斥鎖.
pthread_mutex_t
pthread_mutex_t是一個(gè)比較強(qiáng)大的鎖, 里面封裝了多個(gè)類型的鎖. 普通鎖, 遞歸鎖. 也可以為鎖添加條件
- (void)viewDidLoad {
[super viewDidLoad];
pthread_mutex_init(&(_lock), NULL);
[self beginSaleTicket];
}
- (void)saleTickets {
pthread_mutex_lock(&_lock);
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
pthread_mutex_unlock(&_lock);
}
眼尖的朋友應(yīng)該看出來了.pthread_mutex_init可以傳入兩個(gè)參數(shù), 第二個(gè)參數(shù)指定了鎖的類型. 還記得上面提到的同一線程中不同任務(wù)多次加鎖導(dǎo)致的死鎖問題嗎? pthread_mutex_init提供了解決方案. 那就是 遞歸鎖
遞歸鎖
- (void)viewDidLoad {
[super viewDidLoad];
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&(_lock), &attr);
pthread_mutexattr_destroy(&attr);
[self deadLock];
NSLog(@"test");
}
- (void)deadLock {
pthread_mutex_lock(&_lock);
[self lockAgain];
NSLog(@"等待lockAgain執(zhí)行完成");
pthread_mutex_unlock(&_lock);
}
- (void)lockAgain {
pthread_mutex_lock(&_lock);
NSLog(@"加鎖");
pthread_mutex_unlock(&_lock);
}
可以看到, 當(dāng)設(shè)置pthread_mutex_lock為遞歸鎖時(shí), 任務(wù)可以順利執(zhí)行. 遞歸鎖允許同一線程中的不同任務(wù)對(duì)同一把鎖多次上鎖.
條件
給鎖添加條件時(shí), 線程對(duì)當(dāng)前持有的鎖進(jìn)行釋放, 然后進(jìn)行休眠, 等待其他線程釋放條件信號(hào),就會(huì)喚醒該線程, 重新加鎖并繼續(xù)執(zhí)行.
假設(shè)我們有一個(gè)場(chǎng)景: 搬磚糊墻. 當(dāng)有磚的時(shí)候, 才可以開始糊墻.假設(shè)糊墻跟搬磚的是兩個(gè)人, 當(dāng)糊墻的把磚用完的時(shí)候, 就需要等搬磚的人把磚搬過來才可以繼續(xù)進(jìn)行.
- (void)viewDidLoad {
[super viewDidLoad];
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&_lock, &attr);
pthread_mutexattr_destroy(&attr);
pthread_cond_init(&_cond, NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self build];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self getBrick];
});
}
- (void)build {
pthread_mutex_lock(&_lock);
NSLog(@"開始糊墻");
if (brick == 0) {
// 等待
NSLog(@"沒磚了,等磚來");
pthread_cond_wait(&_cond, &_lock);
}
NSLog(@"糊墻");
pthread_mutex_unlock(&_lock);
}
- (void)getBrick {
pthread_mutex_lock(&_lock);
sleep(1);
brick += 1;
NSLog(@"磚來了");
pthread_cond_signal(&_cond);
pthread_mutex_unlock(&_lock);
}
值得注意的一點(diǎn)是. 條件信號(hào)釋放后, 添加條件的一方并不會(huì)馬上喚醒并加鎖執(zhí)行后續(xù)任務(wù). 而是會(huì)等待信號(hào)發(fā)出方解開當(dāng)前鎖的時(shí)候才會(huì)喚醒. 所以如果有特別耗時(shí)的任務(wù). 可以放在unlock 后面做.
NSLock
- (void)viewDidLoad {
[super viewDidLoad];
self.lock = [NSLock new];
[self beginSaleTicket];
}
- (void)saleTickets {
[self.lock lock];
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
[self.lock unlock];
}
NSLock是對(duì)mutex 普通鎖的封裝, 原理相同.此處就不贅述了
NSRecursiveLock
對(duì)mutex遞歸鎖的封裝,API也與NSLock基本一致.此處就不貼代碼占篇幅了
NSCondition
對(duì)mutex和cond的封裝
- (void)viewDidLoad {
[super viewDidLoad];
self.condition = [NSCondition new];
[self buildWall];
}
- (void)buildWall {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self build];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self getBrick];
});
}
- (void)build {
[self.condition lock];
NSLog(@"開始糊墻");
if (brick == 0) {
// 等待
NSLog(@"沒磚了,搬磚去");
[self.condition wait];
}
NSLog(@"糊墻");
[self.condition unlock];
}
- (void)getBrick {
[self.condition lock];
sleep(1);
brick += 1;
NSLog(@"開始搬磚");
[self.condition signal];
[self.condition unlock];
}
NSConditionLock
對(duì)NSCondition進(jìn)行封裝,可以在同一把鎖上添加不同的條件
- (void)viewDidLoad {
[super viewDidLoad];
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self stepThree];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self stepOne];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self stepTwo];
});
}
- (void)stepOne
{
[self.conditionLock lock];
NSLog(@"%s",__func__);
sleep(1);
[self.conditionLock unlockWithCondition:2];
}
- (void)stepTwo
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"%s",__func__);
sleep(1);
[self.conditionLock unlockWithCondition:3];
}
- (void)stepThree
{
[self.conditionLock lockWhenCondition:3];
NSLog(@"%s",__func__);
[self.conditionLock unlock];
}
@synchronized
是OC的語法糖,本質(zhì)上也是對(duì)mutex的封裝
- (void)viewDidLoad {
[super viewDidLoad];
[self beginSaleTicket];
}
- (void)saleTickets {
@synchronized (self) {
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
}
}
使用@synchronized代碼相當(dāng)簡潔. 通過測(cè)試, @synchronized內(nèi)部實(shí)現(xiàn)的是遞歸鎖.
說完了iOS中提供的鎖,再來說說他們的性能
普通鎖 > 條件鎖 > 遞歸鎖
os_unfair_lock > OSSPinLock > pthread_mutext_t > NSLock > NSCondition > pthread_mutex(recursive) > NSRecursiveLock > NSConditionLock > @synchronized
解決方法2:GCD串行隊(duì)列
上面我們提到,在串行隊(duì)列中,異步任務(wù)是串行執(zhí)行的.所以我們可以新建一個(gè)GCD的串行隊(duì)列來進(jìn)行賣票操作.
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
[self beginSaleTicket];
}
- (void)saleTickets {
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
}
- (void)beginSaleTicket {
dispatch_async(self.ticketQueue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(self.ticketQueue, ^{
for (int i = 0; i < 30; i++) {
[self saleTickets];
}
});
dispatch_async(self.ticketQueue, ^{
for (int i = 0; i < 40; i++) {
[self saleTickets];
}
});
}
解決方法3:信號(hào)量dispatch_semaphore_t
此處涉及PV操作的概念.
執(zhí)行P操作時(shí), 若 P > 0, 信號(hào)量-1, 執(zhí)行任務(wù), 若 P <= 0, 休眠等待
執(zhí)行V操作, 信號(hào)量+1
我們可以通過設(shè)置信號(hào)量為1, 來設(shè)置并發(fā)線程的最大并發(fā)量為1
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketQueue = dispatch_get_global_queue(0, 0);
self.semaphore = dispatch_semaphore_create(1);
[self beginSaleTicket];
}
- (void)saleTickets {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
tickets = tickets - 1;
NSLog(@"賣出1張票,還剩%d張票",tickets);
dispatch_semaphore_signal(self.semaphore);
}
讀寫安全(多讀單寫)
當(dāng)一個(gè)文件, 我們可以對(duì)它進(jìn)行讀寫的時(shí)候, 就會(huì)出現(xiàn)不安全的狀況, 多條線程同時(shí)寫, 或者一邊讀一邊寫都是安全隱患.
所以我們希望對(duì)文件操作是可以做到
- 可以多條線程同時(shí)讀取文件
- 進(jìn)行寫操作時(shí),不可以讀取文件, 也不允許多條線程進(jìn)行寫入操作
解決方案:dispatch_barrier_async
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
從輸出結(jié)果看到, 會(huì)同時(shí)打印多個(gè)read,但一次只會(huì)打印出一個(gè)write,而且在讀操作時(shí),也不會(huì)進(jìn)行其他操作.
dispatch_barrier_async 通過為并發(fā)隊(duì)列設(shè)置柵欄的方式, 當(dāng)柵欄建立時(shí),其他異步任務(wù)都不允許執(zhí)行.
注意
思考一下,把上面的并發(fā)隊(duì)列換成全局隊(duì)列,會(huì)怎么樣?
self.queue = dispatch_get_global_queue(0, 0);
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881477] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881479] write
2021-06-04 11:53:23.544733+0800 MultiThread[98352:14881480] read
2021-06-04 11:53:23.544743+0800 MultiThread[98352:14881485] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881476] read
2021-06-04 11:53:23.544718+0800 MultiThread[98352:14881482] read
2021-06-04 11:53:23.544766+0800 MultiThread[98352:14881483] read
2021-06-04 11:53:23.544780+0800 MultiThread[98352:14881490] read
2021-06-04 11:53:23.544780+0800 MultiThread[98352:14881489] write
2021-06-04 11:53:23.544830+0800 MultiThread[98352:14881491] read
2021-06-04 11:53:23.544841+0800 MultiThread[98352:14881492] read
2021-06-04 11:53:23.544882+0800 MultiThread[98352:14881493] write
2021-06-04 11:53:23.544905+0800 MultiThread[98352:14881494] read
2021-06-04 11:53:23.545049+0800 MultiThread[98352:14881496] read
2021-06-04 11:53:23.545061+0800 MultiThread[98352:14881495] read
2021-06-04 11:53:23.545064+0800 MultiThread[98352:14881498] read
2021-06-04 11:53:23.545077+0800 MultiThread[98352:14881499] read
2021-06-04 11:53:23.545146+0800 MultiThread[98352:14881501] write
2021-06-04 11:53:23.545083+0800 MultiThread[98352:14881497] write
以上是部分的結(jié)果.可以看出,就算加了GCD柵欄,還是等于異步并發(fā).
值得注意的是, GCD的柵欄只能擋住我們自己創(chuàng)建的并發(fā)隊(duì)列.
并不能擋住全局隊(duì)列. 這是為了安全起見. 如果全局隊(duì)列被一個(gè)及其耗時(shí)的操作block住, 那會(huì)引發(fā)很多其他的問題.
文章很長,感謝觀看. 如果文章有問題,還請(qǐng)不吝賜教.如果對(duì)你有一些幫助, 麻煩點(diǎn)個(gè)贊