? 前言:這可能是史上最全面的一篇iOS 多線程博客了(王婆賣瓜一番??),從多線程的基本概念,進(jìn)程的概念,引出iOS中的四種多線程方案pthread、NSThread、NSOperation和GCD,每一部分都有詳細(xì)的代碼和解釋說明;在GCD中,引出同步、異步、串行隊(duì)列(包括主隊(duì)列)和并發(fā)隊(duì)列概念,并對(duì)他們的六種組合進(jìn)行詳細(xì)的代碼驗(yàn)證和說明,把這些概念安排的明明白白,然后詳細(xì)的說明了GCD常見的其他用法;最后,對(duì)iOS中線程安全的方案進(jìn)行全方面的介紹說明并且配上示例代碼。好了,小編累的吐血去了...
一、多線程概念
1、多線程概念:
? 一個(gè)進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)。多線程可以充分的利用多個(gè)CPU同時(shí)處理任務(wù),提高程序的執(zhí)行效率。
2、進(jìn)程概念:
? 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序,每個(gè)進(jìn)程之間是獨(dú)立的。而線程是應(yīng)用程序中一條任務(wù)的執(zhí)行路徑。
3、進(jìn)程和線程的關(guān)系:
? 1)一個(gè)進(jìn)程可以包含多個(gè)線程;
? 2)一個(gè)進(jìn)程中的所有任務(wù)都是在線程中執(zhí)行;
4、iOS多線程實(shí)現(xiàn)方案:
? 1)pthread:純C語言API,是一套通用的多線程API,適用于Linux、Unix、Windows等系統(tǒng),線程生命周期由程序員管理。在iOS實(shí)際開發(fā)中,使用較少。
? 2)NSThread:使用更加面向?qū)ο螅⒖芍苯硬僮骶€程對(duì)象,線程生命周期由程序員管理,項(xiàng)目開發(fā)中使用較多;
? 3)NSOperation:基于GCD,使用更加面向?qū)ο螅€程生命周期系統(tǒng)自動(dòng)管理,使用較多;
? 4)GCD:一套改進(jìn)的C語言多線程API,能充分利用設(shè)備的多核優(yōu)勢(shì),線程生命周期系統(tǒng)自動(dòng)管理,使用最多;
二、pThread
? 純C語言API,使用較為麻煩。
#import <pthread/pthread.h>
- (void)viewDidLoad {
[super viewDidLoad];
[self pthread];
}
//開啟新的線程執(zhí)行run方法
- (void)pthread {
pthread_t thread;
pthread_create(&thread, NULL, run, NULL);
}
void * run(void *param){
NSLog(@"Thread = %@", [NSThread currentThread]);
return NULL;
}
三、NSThread
1、NSThread的創(chuàng)建
? 一個(gè)NSThread對(duì)象就代表一個(gè)線程,有三種方式創(chuàng)建:
? 1)創(chuàng)建線程后需要start啟動(dòng)線程;
? 2)創(chuàng)建線程后自動(dòng)啟動(dòng)線程;
? 3)隱式創(chuàng)建并啟動(dòng)線程;
- (void)viewDidLoad {
[super viewDidLoad];
/*
創(chuàng)建線程,線程對(duì)象(局部變量)系統(tǒng)會(huì)自己加持,在任務(wù)執(zhí)行完之前不會(huì)被銷毀
當(dāng)線程任務(wù)執(zhí)行完畢,線程自動(dòng)銷毀
*/
// 1、使用start啟動(dòng)線程
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"thread1"];
thread1.name = @"thread1";
[thread1 start];
// 2、自動(dòng)啟動(dòng)線程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"thread2"];
// 3、隱式啟動(dòng)線程
[self performSelectorInBackground:@selector(run:) withObject:@"thread3"];
}
- (void)run:(NSString *)threadStr {
if ([threadStr isEqualToString:@"thread1"]) {
NSLog(@"thread1 = %@", [NSThread currentThread]);
} else if ([threadStr isEqualToString:@"thread2"]) {
NSLog(@"thread2 = %@", [NSThread currentThread]);
} else if ([threadStr isEqualToString:@"thread3"]) {
NSLog(@"thread3 = %@", [NSThread currentThread]);
}
}
運(yùn)行程序,打印結(jié)果:
thread1 = <NSThread: 0x60000081e180>{number = 6, name = thread1}
thread2 = <NSThread: 0x60000081e140>{number = 7, name = (null)}
thread3 = <NSThread: 0x60000081e040>{number = 8, name = (null)}
2、NSThread常用用法
//獲得當(dāng)前線程
NSThread *current = [NSThread currentThread];
+ (NSThread *)mainThread; // 獲得主線程
- (BOOL)isMainThread; // 是否為主線程
+ (BOOL)isMainThread; // 是否為主線程
//線程的名字,適用于第一種方式創(chuàng)建的線程(創(chuàng)建的時(shí)候返回NSThread的對(duì)象)
- (void)setName:(NSString *)n;
- (NSString *)name;
3、NSThread的線程通信
? 在開發(fā)中,我們經(jīng)常會(huì)在子線程進(jìn)行耗時(shí)操作,操作結(jié)束后再回到主線程去刷新 UI。這就涉及到了子線程和主線程之間的通信,常用兩個(gè)API如下:
/**
回到主線程執(zhí)行任務(wù),RunLoop模式默認(rèn)是kCFRunLoopCommonModes
@param aSelector:選擇器(方法)
@param arg:傳遞的參數(shù)
@param wait:是否等待任務(wù)執(zhí)行完畢
*/
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
/**
在指定線程中執(zhí)行任務(wù),RunLoop模式默認(rèn)是kCFRunLoopCommonModes
@param aSelector:選擇器(方法)
@param thr:指定的線程
@param arg:傳遞的參數(shù)
@param wait:是否等待任務(wù)執(zhí)行完畢
*/
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
模擬子線程下載,主線程刷新UI代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建新線程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runTestThread) object:nil];
[thread start];
// 注意:這里waitUntilDone只能是NO,不然程序閃退
[self performSelector:@selector(runTestThread) onThread:thread withObject:nil waitUntilDone:NO];
}
// 子線程,執(zhí)行耗時(shí)操作
- (void)runTestThread {
NSLog(@"runTestThread = %@", [NSThread currentThread]);
sleep(3);
// waitUntilDone如果是YES,那么會(huì)阻塞當(dāng)前線程,waitUntilDone會(huì)最后打印
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:NO];
NSLog(@"waitUntilDone...");
}
// 主線程,刷新UI
- (void)runMainThread {
NSLog(@"runMainThread = %@", [NSThread currentThread]);
}
程序運(yùn)行打印結(jié)果:
runTestThread = <NSThread: 0x600001fccc40>{number = 6, name = (null)}
waitUntilDone... // waitUntilDone如果是YES,那么會(huì)阻塞當(dāng)前線程,waitUntilDone會(huì)最后打印
runMainThread = <NSThread: 0x600001fa2180>{number = 1, name = main}
四、NSOperation
? NSOperation和NSOperationQueue是對(duì)GCD的一層封裝,NSOperation是個(gè)抽象類,并不具備封裝操作的能力,必須使用它的子類NSInvocationOperation和NSBlockOperation。
1、NSInvocationOperation
? NSInvocationOperation代碼:
- (void)viewDidLoad {
[super viewDidLoad];
/**
默認(rèn)情況下,調(diào)用了start方法后并不會(huì)開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
只有將NSOperation放到一個(gè)NSOperationQueue中,才會(huì)異步執(zhí)行操作
*/
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[op start];
}
- (void)run {
NSLog(@"mainThread = %@", [NSThread currentThread]);
}
運(yùn)行結(jié)果:
// 說明還是當(dāng)前的線程,并沒有開啟新線程
mainThread = <NSThread: 0x6000036ed600>{number = 1, name = main}
2、NSBlockOperation
? NSBlockOperation代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 通過一個(gè)block創(chuàng)建NSBlockOperation實(shí)例
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
/**
如果NSBlockOperation沒有再添加block,即只有一個(gè)操作數(shù),那么這里肯定是主線程
如果NSBlockOperation封裝的操作數(shù) > 1,就會(huì)異步執(zhí)行操作,那么實(shí)際測(cè)試,這里不一定是主線程
*/
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}];
// 開始
[op start];
}
運(yùn)行結(jié)果:Thread1、Thread2和Thread3當(dāng)中肯定會(huì)有一個(gè)是主線程。
Thread1 = <NSThread: 0x600002e32f40>{number = 4, name = (null)} //不一定是主線程
Thread2 = <NSThread: 0x600002e39380>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600002e64680>{number = 1, name = main}
3、NSOperationQueue
? NSOperation可以調(diào)用start方法來執(zhí)行任務(wù),但默認(rèn)是同步執(zhí)行的,如果將NSOperation添加到NSOperationQueue(操作隊(duì)列)中,系統(tǒng)會(huì)自動(dòng)異步執(zhí)行操作。
? 1)NSInvocationOperation與NSOperationQueue組合:
- (void)viewDidLoad {
[super viewDidLoad];
// 1、創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2、創(chuàng)建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
// 3、添加任務(wù)到隊(duì)列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
}
- (void)run1 {
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}
- (void)run2 {
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}
運(yùn)行結(jié)果:Thread1和Thread2都是在子線程,而不是在主線程,是異步的。
Thread1 = <NSThread: 0x60000111e440>{number = 5, name = (null)}
Thread2 = <NSThread: 0x600001115840>{number = 6, name = (null)}
? 2)NSBlockOperation與NSOperationQueue組合:
- (void)viewDidLoad {
[super viewDidLoad];
// 1、創(chuàng)建隊(duì)列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2、創(chuàng)建NSBlockOperation
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Thread4 = %@", [NSThread currentThread]);
}];
// 3、添加任務(wù)到隊(duì)列中
[queue addOperation:op1];
[queue addOperation:op2];
}
運(yùn)行結(jié)果:Thread1、Thread2、Thread3、Thread4都是子線程,所以添加到隊(duì)列之后,是異步的。
Thread4 = <NSThread: 0x6000020d9c80>{number = 6, name = (null)}
Thread2 = <NSThread: 0x6000020a3140>{number = 5, name = (null)}
Thread3 = <NSThread: 0x6000020dcec0>{number = 7, name = (null)}
Thread1 = <NSThread: 0x6000020bc740>{number = 4, name = (null)}
4、控制NSOperationQueue是串行隊(duì)列還是并發(fā)隊(duì)列
? 可以通過設(shè)置maxConcurrentOperationCount的值來選擇串行隊(duì)列還是并發(fā)隊(duì)列。
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
/**
maxCount等于-1:默認(rèn)值,也就是不限制最大并發(fā)數(shù),添加的operation都是異步的
maxCount等于0:不執(zhí)行operation
maxCount等于1:在子線程同步執(zhí)行operation,也就是串行隊(duì)列
maxCount大于1:在指定數(shù)量的線程內(nèi)異步處理operation
maxCount為負(fù)數(shù),且不等于-1:程序拋出異常,count cannot be negative
*/
queue.maxConcurrentOperationCount = -1;
// 添加操作
[queue addOperationWithBlock:^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
// 切換到主線程
NSOperationQueue *main = [NSOperationQueue mainQueue];
[main addOperationWithBlock:^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}];
}];
[queue addOperationWithBlock:^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}];
}
運(yùn)行結(jié)果:
Thread1 = <NSThread: 0x600002d93440>{number = 4, name = (null)}
Thread3 = <NSThread: 0x600002dd1fc0>{number = 6, name = (null)}
Thread2 = <NSThread: 0x600002d86200>{number = 1, name = main}
5、NSOperation之間可以設(shè)置依賴來保證執(zhí)行順序
? 比如一定要讓操作A執(zhí)行完后,才能執(zhí)行操作B,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}];
/**
1、[op1 addDependency:op2]:op1依賴于op2執(zhí)行完成
2、如果同時(shí)設(shè)置op1依賴于op2,op2依賴于op1,會(huì)造成死鎖,不會(huì)執(zhí)行任務(wù)了
3、任務(wù)可以跨隊(duì)列依賴,在不同隊(duì)列里面的任務(wù)也可以相互依賴
*/
[op1 addDependency:op2];
[queue addOperation:op1];
[queue addOperation:op2];
}
運(yùn)行結(jié)果:先執(zhí)行op2(Thread2、Thread3),后執(zhí)行op1(Thread1)。
Thread3 = <NSThread: 0x600002cb8ac0>{number = 7, name = (null)}
Thread2 = <NSThread: 0x600002cbcc00>{number = 6, name = (null)}
Thread1 = <NSThread: 0x600002cbcc00>{number = 6, name = (null)}
五、GCD
? Grand Central Dispatch (GCD)是Apple開發(fā)的一個(gè)多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng),它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù),GCD是一個(gè)替代諸如NSThread等技術(shù)的很高效和強(qiáng)大的技術(shù)。
? GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核、四核),GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。
? GCD兩個(gè)核心概念,任務(wù)和隊(duì)列,任務(wù)包括(同步執(zhí)行任務(wù)、異步執(zhí)行任務(wù)),隊(duì)列包括(串行隊(duì)列、并發(fā)隊(duì)列),隊(duì)列采用 FIFO(First In First Out)的原則,即先進(jìn)先出原則。
1、同步執(zhí)行、異步執(zhí)行
? 同步執(zhí)行與異步執(zhí)行的區(qū)別:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。
? 1)同步執(zhí)行(sync):同步添加任務(wù)到指定的隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
? 2)異步執(zhí)行(async):異步添加任務(wù)到指定的隊(duì)列中,它不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù)。可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。
? 注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務(wù)所指定的隊(duì)列類型有關(guān)。
2、串行隊(duì)列、并發(fā)隊(duì)列
? 串行隊(duì)列和并發(fā)隊(duì)列的區(qū)別:執(zhí)行順序不同,以及開啟線程數(shù)不同。
? 1)串行隊(duì)列(Serial Dispatch Queue):讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(最多創(chuàng)建一個(gè)線程)。dispatch_get_main_queue() 主隊(duì)列就是一個(gè)串行隊(duì)列。
? 2)并發(fā)隊(duì)列(Concurrent Dispatch Queue):可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(可以開啟多個(gè)線程)。dispatch_get_global_queue(0, 0) 全局隊(duì)列就是并發(fā)隊(duì)列。
? 注意:并發(fā)隊(duì)列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效。
3、經(jīng)典各種組合模式
? 本來同步、異步、串行、并發(fā)有四種組合,但是當(dāng)前代碼默認(rèn)放在主隊(duì)列中,全局并發(fā)隊(duì)列可以作為普通并發(fā)隊(duì)列來使用,所以主隊(duì)列很有必要專門來研究一下,所以我們就有六種組合模式了。
? 1)同步執(zhí)行 + 串行隊(duì)列:沒有開啟新線程,串行執(zhí)行任務(wù)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。
Thread1 = <NSThread: 0x600003627900>{number = 1, name = main}
Thread2 = <NSThread: 0x600003627900>{number = 1, name = main}
Thread3 = <NSThread: 0x600003627900>{number = 1, name = main}
? 2)同步執(zhí)行 + 并發(fā)隊(duì)列:沒有開啟新線程,串行執(zhí)行任務(wù)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。
Thread1 = <NSThread: 0x6000011ea140>{number = 1, name = main}
Thread2 = <NSThread: 0x6000011ea140>{number = 1, name = main}
Thread3 = <NSThread: 0x6000011ea140>{number = 1, name = main}
? 3)異步執(zhí)行 + 串行隊(duì)列:有開啟新線程(1條),串行執(zhí)行任務(wù)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:有開啟一條新線程,Thread1、Thread2、Thread3按順序執(zhí)行。
Thread1 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}
Thread2 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}
Thread3 = <NSThread: 0x6000006a4340>{number = 6, name = (null)}
? 4)異步執(zhí)行 + 并發(fā)隊(duì)列:有開啟新線程(多條),并發(fā)執(zhí)行任務(wù)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:有開啟多條新線程,Thread1、Thread2、Thread3是并發(fā)執(zhí)行的。
Thread2 = <NSThread: 0x600003c65800>{number = 7, name = (null)}
Thread1 = <NSThread: 0x600003c74840>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600003c45940>{number = 5, name = (null)}
? 5)同步執(zhí)行+主隊(duì)列:死鎖卡住不執(zhí)行。其實(shí)如果在串行隊(duì)列中嵌套同步使用串行隊(duì)列,也會(huì)發(fā)生死鎖,原理和這個(gè)類似。所以項(xiàng)目中數(shù)據(jù)庫處理FMDataQueue嵌套使用時(shí),需要注意死鎖問題。
? 如果不是在主線程執(zhí)行同步執(zhí)行+主隊(duì)列呢,那么不會(huì)發(fā)生死鎖(讀者可以自己代碼測(cè)試驗(yàn)證)。
- (void)viewDidLoad {
[super viewDidLoad];
/**
dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{ //異步串行,創(chuàng)建一條新線程
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_sync(serialQueue, ^{ //嵌套同步串行,發(fā)生死鎖
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
});
*/
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:程序死鎖崩潰,原因是默認(rèn)viewDidLoad被添加到主隊(duì)列中(先運(yùn)行完viewDidLoad,后運(yùn)行添加的任務(wù)),然后又同步添加Thread1任務(wù)到主隊(duì)列中(先運(yùn)行Thread1任務(wù),后運(yùn)行viewDidLoad任務(wù)),造成任務(wù)相互等待卡死,程序死鎖崩潰。
? 6)異步執(zhí)行+主隊(duì)列:沒有開啟新線程,串行執(zhí)行任務(wù)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:線程還是main主線程,沒有開啟新線程,Thread1、Thread2、Thread3按順序執(zhí)行。
Thread1 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
Thread2 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
Thread3 = <NSThread: 0x6000006ac0c0>{number = 1, name = main}
4、GCD線程間的通信
? 在項(xiàng)目開發(fā)中,一般耗時(shí)的操作在別的線程處理,然后在主線程刷新UI,GCD線程間的通信如下:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2); //模擬耗時(shí)操作
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
});
}
運(yùn)行結(jié)果:在子線程執(zhí)行耗時(shí)操作,再回到主線程刷新UI。
Thread1 = <NSThread: 0x600002189400>{number = 5, name = (null)}
Thread2 = <NSThread: 0x6000021dc0c0>{number = 1, name = main}
5、GCD其他的常見用法
? 1)柵欄方法(dispatch_barrier_async)
? 我們有時(shí)需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作,這就需要用到dispatch_barrier_async 方法在兩個(gè)操作組間形成柵欄。NSOperation的 addDependency 也是這個(gè)效果。
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建并發(fā)隊(duì)列
dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1); //模擬耗時(shí)操作
NSLog(@"Thread1 = %@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"Thread2 = %@",[NSThread currentThread]);
});
// 添加?xùn)艡? dispatch_barrier_async(concurrentQueue, ^{
sleep(1);
NSLog(@"Thread3 = %@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"Thread4 = %@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"Thread5 = %@",[NSThread currentThread]);
});
}
運(yùn)行結(jié)果:先執(zhí)行第一組任務(wù)(Thread1、Thread2),然后再執(zhí)行柵欄的第二組任務(wù)(Thread3、Thread4、Thread5)。
Thread2 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}
Thread1 = <NSThread: 0x60000129c540>{number = 7, name = (null)}
Thread3 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}
Thread5 = <NSThread: 0x6000012af680>{number = 6, name = (null)}
Thread4 = <NSThread: 0x6000012ad780>{number = 4, name = (null)}
? 2)延時(shí)方法(dispatch_after)
? 項(xiàng)目中我們有時(shí)需要幾秒之后再執(zhí)行某個(gè)方法,那么可以使用GCD的延時(shí)方法,而不用創(chuàng)建一個(gè)定時(shí)器來處理。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0 秒后異步追加任務(wù)代碼到主隊(duì)列,并開始執(zhí)行,如果主線程被阻塞,時(shí)間上不會(huì)非常準(zhǔn)確
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
sleep(5); //阻塞主線程
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}
運(yùn)行結(jié)果:先執(zhí)行Thread1,5s之后執(zhí)行Thread3,當(dāng)前主隊(duì)列執(zhí)行完畢,然后執(zhí)行dispatch_after添加的任務(wù)Thread2。至于為什么Thread3、Thread2時(shí)間接近,而不是相差2s,因?yàn)閳?zhí)行完Thread1的2s之后,Thread2添加到主隊(duì)列,正在等待主隊(duì)列執(zhí)行完畢。
2019-10-24 10:03:05.638019 Thread1 = <NSThread: 0x6000005be200>{number = 1, name = main}
2019-10-24 10:03:10.639501 Thread3 = <NSThread: 0x6000005be200>{number = 1, name = main}
2019-10-24 10:03:10.658852 Thread2 = <NSThread: 0x6000005be200>{number = 1, name = main}
假如注釋掉 sleep(5),那么運(yùn)行結(jié)果:主隊(duì)列沒被阻塞,Thread2任務(wù)基本2s之后就會(huì)執(zhí)行。
2019-10-24 10:10:55.653691 Thread1 = <NSThread: 0x600000c44680>{number = 1, name = main}
2019-10-24 10:10:55.653898 Thread3 = <NSThread: 0x600000c44680>{number = 1, name = main}
2019-10-24 10:10:57.654041 Thread2 = <NSThread: 0x600000c44680>{number = 1, name = main}
? 3)執(zhí)行一次(dispatch_once)
? 項(xiàng)目開發(fā)中,我們經(jīng)常使用單例模式,那么就對(duì) dispatch_once 很熟悉,代碼只運(yùn)行一次。并且即使在多線程的環(huán)境下,dispatch_once 也可以保證線程安全。
- (void)viewDidLoad {
[super viewDidLoad];
[self executeOnce]; //先執(zhí)行一次
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Thread1 = %@", [NSThread currentThread]);
[self executeOnce];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
[self executeOnce];
});
}
- (void)executeOnce {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只會(huì)執(zhí)行一次
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運(yùn)行結(jié)果:Thread3 只被執(zhí)行一次,并且是線程安全的。
Thread3 = <NSThread: 0x600002f21dc0>{number = 1, name = main}
Thread1 = <NSThread: 0x600002f70300>{number = 5, name = (null)}
Thread2 = <NSThread: 0x600002f6c200>{number = 3, name = (null)}
? 4)快速迭代方法(dispatch_apply)
? 通常我們會(huì)用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中,并等待全部任務(wù)執(zhí)行結(jié)束。如果是在串行隊(duì)列中使用dispatch_apply,那么就和 for 循環(huán)一樣,按順序同步執(zhí)行,所以實(shí)際使用的時(shí)候一般用并發(fā)隊(duì)列。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //全局并發(fā)隊(duì)列
NSLog(@"dispatch_apply begin");
dispatch_apply(5, queue, ^(size_t index) {
sleep(1);
NSLog(@"Thread_%ld = %@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply end");
}
運(yùn)行結(jié)果:先異步執(zhí)行完所有的任務(wù),最后執(zhí)行dispatch_apply end。
dispatch_apply begin
Thread_0 = <NSThread: 0x6000028dc040>{number = 4, name = (null)}
Thread_2 = <NSThread: 0x6000028f4700>{number = 6, name = (null)}
Thread_1 = <NSThread: 0x6000028cdc40>{number = 5, name = (null)}
Thread_3 = <NSThread: 0x600002880040>{number = 1, name = main}
Thread_4 = <NSThread: 0x6000028dc040>{number = 4, name = (null)}
dispatch_apply end
? 5)隊(duì)列組(dispatch_group)
? 有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行2個(gè)耗時(shí)任務(wù),然后當(dāng)2個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù),這時(shí)候我們可以用到 GCD 的隊(duì)列組。項(xiàng)目中常見使用場(chǎng)景有:請(qǐng)求多個(gè)接口數(shù)據(jù)返回后,再統(tǒng)一進(jìn)行刷新;下載完多張圖片后,再統(tǒng)一進(jìn)行合并繪制等。
? 調(diào)用隊(duì)列組的 dispatch_group_async 先把任務(wù)放到隊(duì)列中,然后將隊(duì)列放入隊(duì)列組中。或者使用隊(duì)列組的 dispatch_group_enter、dispatch_group_leave 組合來實(shí)現(xiàn) dispatch_group_async。
? 調(diào)用隊(duì)列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)。或者使用 dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"Thread1 = %@",[NSThread currentThread]);
});
dispatch_group_async(group, globalQueue, ^{
sleep(1);
NSLog(@"Thread2 = %@",[NSThread currentThread]);
});
/**
通過enter、leave實(shí)現(xiàn)
dispatch_group_enter:標(biāo)志著一個(gè)任務(wù)追加到 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) +1
dispatch_group_leave:標(biāo)志著一個(gè)任務(wù)離開了 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) -1
當(dāng) group 中未執(zhí)行任務(wù)數(shù)為0的時(shí)候,才會(huì)使 dispatch_group_wait 解除阻塞,以及執(zhí)行追加到 dispatch_group_notify 中的任務(wù)
*/
dispatch_group_enter(group);
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"Thread3 = %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
// 通知
dispatch_group_notify(group, mainQueue, ^{
// 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后,回到主線程執(zhí)行下邊任務(wù)
sleep(1);
NSLog(@"Thread4 = %@",[NSThread currentThread]);
});
NSLog(@"dispatch_group_wait 之前??");
// 設(shè)置DISPATCH_TIME_FOREVER會(huì)一直等待group結(jié)束,會(huì)阻塞當(dāng)前線程,如果設(shè)置為DISPATCH_TIME_NOW就不會(huì)等待
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group 執(zhí)行完畢");
}
運(yùn)行結(jié)果:先異步執(zhí)行g(shù)roup任務(wù)(Thread1、Thread2、Thread3),group執(zhí)行完畢后,再執(zhí)行nofity的任務(wù)。
dispatch_group_wait 之前??
Thread2 = <NSThread: 0x600003ab8b00>{number = 6, name = (null)}
Thread3 = <NSThread: 0x600003abc600>{number = 4, name = (null)}
Thread1 = <NSThread: 0x600003a8c540>{number = 7, name = (null)}
group 執(zhí)行完畢
Thread4 = <NSThread: 0x600003ae2140>{number = 1, name = main}
? 6)信號(hào)量(dispatch_semaphore)
? 項(xiàng)目開發(fā)中,有時(shí)候有這樣的需求:異步執(zhí)行耗時(shí)任務(wù),并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號(hào)量的方式,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks,然后再返回該 tasks。
? GCD 中的信號(hào)量是指 Dispatch Semaphore,是持有計(jì)數(shù)的信號(hào)。在 Dispatch Semaphore 中,使用計(jì)數(shù)來完成這個(gè)功能,計(jì)數(shù)小于 0 時(shí)等待,不可通過。計(jì)數(shù)為 0 或大于 0 時(shí),不等待,可通過。信號(hào)量主要用于:
? a、保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù);
? b、保證線程安全,為線程加鎖;
// AFNetworking 部分源碼
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
我們自己用信號(hào)量實(shí)現(xiàn)線程同步:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"dispatch_semaphore begin");
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
/**
創(chuàng)建信號(hào)量
小于0:程序閃退
等于0:正常使用,會(huì)同步執(zhí)行代碼
大于0:不會(huì)同步執(zhí)行
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
/**
dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào),讓信號(hào)總量加 1
dispatch_semaphore_wait:可以使總信號(hào)量減 1,信號(hào)總量小于 0 時(shí)就會(huì)一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
*/
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore); //信號(hào)量 +1
});
NSLog(@"dispatch_semaphore_wait 之前??");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //信號(hào)量 -1
NSLog(@"dispatch_semaphore end");
}
運(yùn)行結(jié)果:semaphore初始創(chuàng)建時(shí)計(jì)數(shù)為 0,異步將Thread1任務(wù)添加到全局并發(fā)隊(duì)列,不做等待,接著執(zhí)行 dispatch_semaphore_wait 方法,semaphore 減 1,此時(shí) semaphore == -1,當(dāng)前線程進(jìn)入等待狀態(tài)。
? 然后,異步任務(wù) 1 開始執(zhí)行。任務(wù) 1 執(zhí)行到 dispatch_semaphore_signal 之后,總信號(hào)量加 1,此時(shí) semaphore == 0,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行,最后打印end。
dispatch_semaphore begin
dispatch_semaphore_wait 之前??
Thread1 = <NSThread: 0x6000021588c0>{number = 4, name = (null)}
dispatch_semaphore end
我們自己用信號(hào)量實(shí)現(xiàn) semaphore 加鎖:
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 10;
/**
注意:這里參數(shù)是1
信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步
*/
self.semaphoreLock = dispatch_semaphore_create(1); //
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
dispatch_async(globalQueue, ^{ //第一條線程
[self sellTickets];
});
dispatch_async(globalQueue, ^{ //第二條線程
[self sellTickets];
});
dispatch_async(globalQueue, ^{ //第三條線程
[self sellTickets];
});
}
- (void)sellTickets {
while (1) { //一直運(yùn)行,直到break退出
// 等待信號(hào)量,類似于加鎖,此時(shí)信號(hào)量減一,如果信號(hào)量減到小于0,那么就等待一個(gè)新的信號(hào)量發(fā)送
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketCount > 0) { //如果還有票,繼續(xù)售賣
self.ticketCount --;
NSLog(@"剩余票數(shù) = %ld, 線程 = %@", self.ticketCount, [NSThread currentThread]);
[NSThread sleepForTimeInterval:0.2]; //模擬耗時(shí)操作
} else { //如果已賣完,關(guān)閉售票窗口
NSLog(@"火車票售完");
// 發(fā)送一個(gè)信號(hào)量,類似于解鎖
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
// 發(fā)送一個(gè)信號(hào)量,類似于解鎖
dispatch_semaphore_signal(self.semaphoreLock);
}
}
運(yùn)行結(jié)果:ticketCount 按照順序依次遞減1,“火車票售完”打印三次是因?yàn)橛腥龡l線程運(yùn)行。
? 分析過程:首先三條線程都并發(fā)執(zhí)行sellTickets方法,最快的一條線程首先執(zhí)行dispatch_semaphore_wait,信號(hào)量減一,此時(shí)信號(hào)量為0,該條線程繼續(xù)執(zhí)行wait之后的代碼,而其他較慢的兩條線程進(jìn)行等待新的信號(hào)量出現(xiàn),較快的一條線程賣票之后,發(fā)送一個(gè)信號(hào)量,那么較慢的兩條線程其中的一條執(zhí)行wait之后的代碼,如此循環(huán),保證一個(gè)時(shí)間點(diǎn)只有一條線程進(jìn)行賣票,從而保證線程安全。
剩余票數(shù) = 9, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 8, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 7, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 5, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
剩余票數(shù) = 2, 線程 = <NSThread: 0x600003e6da40>{number = 4, name = (null)}
剩余票數(shù) = 1, 線程 = <NSThread: 0x600003e6d740>{number = 5, name = (null)}
剩余票數(shù) = 0, 線程 = <NSThread: 0x600003e0e840>{number = 6, name = (null)}
火車票售完
火車票售完
火車票售完
如果把信號(hào)量的代碼注釋,運(yùn)行結(jié)果:剩余票數(shù)順序明顯不對(duì),數(shù)據(jù)錯(cuò)亂。
剩余票數(shù) = 8, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 7, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 9, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 6, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 5, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 4, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 3, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
剩余票數(shù) = 2, 線程 = <NSThread: 0x6000004dc040>{number = 4, name = (null)}
剩余票數(shù) = 1, 線程 = <NSThread: 0x6000004f0640>{number = 7, name = (null)}
剩余票數(shù) = 0, 線程 = <NSThread: 0x6000004de5c0>{number = 6, name = (null)}
火車票售完
火車票售完
火車票售完
六、線程安全
? 當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題。線程安全問題:一般使用線程同步技術(shù),同步技術(shù)有加鎖、串行隊(duì)列、信號(hào)量等。
? 串行隊(duì)列,上文已經(jīng)有介紹,比如項(xiàng)目中FMDBQueue,就是在串行隊(duì)列中同步操作數(shù)據(jù)庫,從而保證線程安全。
? 信號(hào)量,上文已經(jīng)有介紹,其實(shí)類似于互斥鎖。
? 下面主要講iOS中常見的鎖:
? 從大的方向講有兩種鎖:自旋鎖、互斥鎖。
? 自旋鎖:線程反復(fù)檢查鎖變量是否可用,是一種忙等狀態(tài),自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對(duì)于線程只會(huì)阻塞很短時(shí)間的場(chǎng)合是有效的。自旋鎖的性能高于互斥鎖,因?yàn)轫憫?yīng)速度快,但是可能發(fā)生優(yōu)先級(jí)反轉(zhuǎn)問題(如果等待鎖的線程優(yōu)先級(jí)較高,它會(huì)一直占用著CPU資源,優(yōu)先級(jí)低的線程就無法釋放鎖)。常見的自旋鎖包括:OSSpinLock、atomic等。
? 互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖,因此適用于線程阻塞時(shí)間較長(zhǎng)的場(chǎng)合。常見的互斥鎖包括:os_unfair_lock、pthread_mutex、dispatch_semaphore、@synchronized,其中pthread_mutex又衍生出NSLock、NSCondition、NSConditionLock、NSRecursiveLock,更加面向?qū)ο蟮逆i。
至于鎖的性能參考這個(gè)博客傳送門,我只把測(cè)試結(jié)果放出來。
1、OSSpinLock
? 自旋鎖OSSpinLock,已經(jīng)在iOS10被蘋果棄用,因?yàn)樗嬖?strong>優(yōu)先級(jí)反轉(zhuǎn)的問題。
#import <libkern/OSAtomic.h>
OSSpinLock lock = OS_SPINLOCK_INIT; //創(chuàng)建鎖
OSSpinLockTry(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
OSSpinLockLock(&lock); //加鎖
// [self sellTickets];
OSSpinLockUnlock(&lock); //解鎖
2、os_unfair_lock
? 這是蘋果iOS10之后推出的新的取代OSSpinLock的鎖。雖然是替代OSSpinLock的,但os_unfair_lock并不是自旋鎖,而是互斥鎖。
#import <os/lock.h>
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; //創(chuàng)建鎖
os_unfair_lock_trylock(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
os_unfair_lock_lock(&lock); //加鎖
// [self sellTickets];
os_unfair_lock_unlock(&lock); //解鎖
3、dispatch_semaphore
? 雖然上面有示例代碼,但是這里也還是把思路寫出來進(jìn)行對(duì)比學(xué)習(xí)。
dispatch_semaphore_t lock = dispatch_semaphore_create(1); //創(chuàng)建鎖
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); //加鎖
// [self sellTickets];
dispatch_semaphore_signal(lock); //解鎖
4、pthread_mutex
? pthread_mutex是c底層的線程鎖,關(guān)于pthread的各種同步機(jī)制,感興趣的可以看看這篇文章pthread的各種同步機(jī)制,可謂講的相當(dāng)全面了。
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //創(chuàng)建鎖
pthread_mutex_trylock(&lock); //嘗試加鎖,加鎖失敗返回NO,成功返回YES
pthread_mutex_lock(&lock); //加鎖
// [self sellTickets];
pthread_mutex_unlock(&lock); //解鎖
在YYKit的YYMemoryCache中,截取部分相關(guān)代碼供大家參考。
pthread_mutex_t _lock; //聲明
pthread_mutex_init(&_lock, NULL); //初始化
pthread_mutex_lock(&_lock); //加鎖
NSUInteger count = _lru->_totalCount;
pthread_mutex_unlock(&_lock); //解鎖
pthread_mutex_destroy(&_lock); //銷毀鎖
5、NSLock、NSCondition、NSConditionLock、NSRecursiveLock
? 以上四種鎖全部是基于pthread_mutex封裝的面向?qū)ο蟮逆i。這幾種鎖都遵守NSLocking協(xié)議,此協(xié)議中提供了加鎖 lock 和解鎖 unlock 方法。
? 1)NSLock
? 比pthread_mutex更加面向?qū)ο螅褂靡埠芎?jiǎn)單。
NSLock *lock = [NSLock new]; //創(chuàng)建鎖
[lock tryLock]; //嘗試加鎖,加鎖失敗返回NO,成功返回YES
[lock lock]; //加鎖
// [self sellTickets];
[lock unlock]; //解鎖
在AFNetworking里的AFURLSessionManager.m文件里,截取一部分NSLock的用法供參考。
@property (readwrite, nonatomic, strong) NSLock *lock;
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
2)NSCondition
條件鎖,它的使用和dispatch_semaphore有異曲同工之妙,API有wait和signal。
- (void)wait; // 線程等待
- (BOOL)waitUntilDate:(NSDate *)limit; // 設(shè)置線程等待時(shí)間,過了這個(gè)時(shí)間就會(huì)自動(dòng)執(zhí)行后面的代碼
- (void)signal; // 喚醒一個(gè)設(shè)置為wait等待的線程
- (void)broadcast; // 喚醒所有設(shè)置為wait等待的線程
- (void)viewDidLoad {
[super viewDidLoad];
// 比如刪除數(shù)組中的所有元素,等數(shù)組有元素時(shí)再去刪除
NSCondition *lock = [[NSCondition alloc] init];
NSMutableArray *array = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //線程1
[lock lock];
while (!array.count) {
[lock wait]; //等待
}
[array removeAllObjects];
NSLog(@"array removeAllObjects");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //線程2
sleep(1); //以保證讓線程2的代碼后執(zhí)行
[array addObject:@"Hello world!"];
NSLog(@"array addObject:Hello world!");
[lock signal]; //發(fā)送信號(hào)
});
}
3)NSConditionLock
條件鎖,可以設(shè)置更多的條件。
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化NSConditionLock,并且condition設(shè)置為0
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if([lock tryLockWhenCondition:0]){ //當(dāng)condition為0時(shí),嘗試加鎖
NSLog(@"Thread1 = %@", [NSThread currentThread]);
[lock unlockWithCondition:1]; //解鎖,并且設(shè)置condition為1
}else{
NSLog(@"失敗");
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lockWhenCondition:3];
NSLog(@"Thread2 = %@", [NSThread currentThread]);
[lock unlockWithCondition:2];
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[lock lockWhenCondition:1];
NSLog(@"Thread3 = %@", [NSThread currentThread]);
[lock unlockWithCondition:3];
});
}
運(yùn)行結(jié)果:condition初始化是0,所以先加鎖執(zhí)行Thread1,然后解鎖并且設(shè)置condition為1;接著當(dāng)condition為1時(shí),先加鎖執(zhí)行Thread3,然后解鎖并且設(shè)置condition為3;最后類似的,執(zhí)行Thread2。
根據(jù)NSConditionLock的這種特性,可以用來做多線程任務(wù)之間的依賴。
Thread1 = <NSThread: 0x60000328c640>{number = 7, name = (null)}
Thread3 = <NSThread: 0x6000032b62c0>{number = 4, name = (null)}
Thread2 = <NSThread: 0x6000032b65c0>{number = 5, name = (null)}
? 4)NSRecursiveLock
? 遞歸鎖,遞歸鎖有一個(gè)特點(diǎn),就是同一個(gè)線程可以加鎖N次而不會(huì)引發(fā)死鎖。
NSRecursiveLock *lock = [NSRecursiveLock new];
[lock tryLock];
[lock lock];
// [self sellTickets];
[lock unlock];
當(dāng)然嘍,pthread_mutex鎖也支持遞歸,只需要設(shè)置PTHREAD_MUTEX_RECURSIVE即可。
pthread_mutex_t lock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); //初始化attr
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //綁定遞歸type
pthread_mutex_init(&lock, &attr); //初始化遞歸鎖
pthread_mutexattr_destroy(&attr); //銷毀attr
pthread_mutex_lock(&lock); //加鎖
// [self sellTickets];
pthread_mutex_unlock(&lock); //解鎖
pthread_mutex_destroy(&lock); //結(jié)束時(shí)銷毀鎖
6、pthread_rwlock
? 讀寫鎖,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,可以保證讀寫的正確性。如果需要實(shí)現(xiàn)“多讀單寫”功能,也可以用GCD的柵欄方法。
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //創(chuàng)建讀寫鎖
pthread_rwlock_rdlock(&rwlock); //讀鎖
pthread_rwlock_wrlock(&rwlock); //寫鎖
pthread_rwlock_unlock(&rwlock); //解鎖
7、@synchronized
? @synchronized,雖然在所有鎖里面性能最差,但是代碼最簡(jiǎn)單,所以項(xiàng)目實(shí)際使用很常見。
@synchronized (self) {
// [self sellTickets];
}
總結(jié)一下:
? 1)大部分鎖都是先創(chuàng)建鎖,然后加鎖、解鎖等流程;
? 2)如果對(duì)性能有要求,優(yōu)先使用dispatch_semaphore或者pthread_mutex,如果對(duì)性能無要求,一般使用最簡(jiǎn)單的@synchronized即可。