? 前言:這可能是史上最全面的一篇iOS 多線程博客了(王婆賣瓜一番??),從多線程的基本概念,進(jìn)程的概念,引出iOS中的四種多線程方案pthread、NSThread、NSOperation和GCD,每一部分都有詳細(xì)的代碼和解釋說明;在GCD中,引出同步、異步、串行隊列(包括主隊列)和并發(fā)隊列概念,并對他們的六種組合進(jìn)行詳細(xì)的代碼驗證和說明,把這些概念安排的明明白白,然后詳細(xì)的說明了GCD常見的其他用法;最后,對iOS中線程安全的方案進(jìn)行全方面的介紹說明并且配上示例代碼。好了,小編累的吐血去了...
一、多線程概念
1、多線程概念:
? 一個進(jìn)程中可以開啟多條線程,每條線程可以并行執(zhí)行不同的任務(wù)。多線程可以充分的利用多個CPU同時處理任務(wù),提高程序的執(zhí)行效率。
2、進(jìn)程概念:
? 進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,每個進(jìn)程之間是獨立的。而線程是應(yīng)用程序中一條任務(wù)的執(zhí)行路徑。
3、進(jìn)程和線程的關(guān)系:
? 1)一個進(jìn)程可以包含多個線程;
? 2)一個進(jìn)程中的所有任務(wù)都是在線程中執(zhí)行;
4、iOS多線程實現(xiàn)方案:
? 1)pthread:純C語言API,是一套通用的多線程API,適用于Linux、Unix、Windows等系統(tǒng),線程生命周期由程序員管理。在iOS實際開發(fā)中,使用較少。
? 2)NSThread:使用更加面向?qū)ο?,并可直接操作線程對象,線程生命周期由程序員管理,項目開發(fā)中使用較多;
? 3)NSOperation:基于GCD,使用更加面向?qū)ο?,線程生命周期系統(tǒng)自動管理,使用較多;
? 4)GCD:一套改進(jìn)的C語言多線程API,能充分利用設(shè)備的多核優(yōu)勢,線程生命周期系統(tǒ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)建
? 一個NSThread對象就代表一個線程,有三種方式創(chuàng)建:
? 1)創(chuàng)建線程后需要start啟動線程;
? 2)創(chuàng)建線程后自動啟動線程;
? 3)隱式創(chuàng)建并啟動線程;
- (void)viewDidLoad {
[super viewDidLoad];
/*
創(chuàng)建線程,線程對象(局部變量)系統(tǒng)會自己加持,在任務(wù)執(zhí)行完之前不會被銷毀
當(dāng)線程任務(wù)執(zhí)行完畢,線程自動銷毀
*/
// 1、使用start啟動線程
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"thread1"];
thread1.name = @"thread1";
[thread1 start];
// 2、自動啟動線程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"thread2"];
// 3、隱式啟動線程
[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]);
}
}
運行程序,打印結(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)建的時候返回NSThread的對象)
- (void)setName:(NSString *)n;
- (NSString *)name;
3、NSThread的線程通信
? 在開發(fā)中,我們經(jīng)常會在子線程進(jìn)行耗時操作,操作結(jié)束后再回到主線程去刷新 UI。這就涉及到了子線程和主線程之間的通信,常用兩個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í)行耗時操作
- (void)runTestThread {
NSLog(@"runTestThread = %@", [NSThread currentThread]);
sleep(3);
// waitUntilDone如果是YES,那么會阻塞當(dāng)前線程,waitUntilDone會最后打印
[self performSelectorOnMainThread:@selector(runMainThread) withObject:nil waitUntilDone:NO];
NSLog(@"waitUntilDone...");
}
// 主線程,刷新UI
- (void)runMainThread {
NSLog(@"runMainThread = %@", [NSThread currentThread]);
}
程序運行打印結(jié)果:
runTestThread = <NSThread: 0x600001fccc40>{number = 6, name = (null)}
waitUntilDone... // waitUntilDone如果是YES,那么會阻塞當(dāng)前線程,waitUntilDone會最后打印
runMainThread = <NSThread: 0x600001fa2180>{number = 1, name = main}
四、NSOperation
? NSOperation和NSOperationQueue是對GCD的一層封裝,NSOperation是個抽象類,并不具備封裝操作的能力,必須使用它的子類NSInvocationOperation和NSBlockOperation。
1、NSInvocationOperation
? NSInvocationOperation代碼:
- (void)viewDidLoad {
[super viewDidLoad];
/**
默認(rèn)情況下,調(diào)用了start方法后并不會開一條新線程去執(zhí)行操作,而是在當(dāng)前線程同步執(zhí)行操作
只有將NSOperation放到一個NSOperationQueue中,才會異步執(zhí)行操作
*/
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
[op start];
}
- (void)run {
NSLog(@"mainThread = %@", [NSThread currentThread]);
}
運行結(jié)果:
// 說明還是當(dāng)前的線程,并沒有開啟新線程
mainThread = <NSThread: 0x6000036ed600>{number = 1, name = main}
2、NSBlockOperation
? NSBlockOperation代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 通過一個block創(chuàng)建NSBlockOperation實例
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
/**
如果NSBlockOperation沒有再添加block,即只有一個操作數(shù),那么這里肯定是主線程
如果NSBlockOperation封裝的操作數(shù) > 1,就會異步執(zhí)行操作,那么實際測試,這里不一定是主線程
*/
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}];
[op addExecutionBlock:^{
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}];
// 開始
[op start];
}
運行結(jié)果:Thread1、Thread2和Thread3當(dāng)中肯定會有一個是主線程。
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(操作隊列)中,系統(tǒng)會自動異步執(zhí)行操作。
? 1)NSInvocationOperation與NSOperationQueue組合:
- (void)viewDidLoad {
[super viewDidLoad];
// 1、創(chuàng)建隊列
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ù)到隊列中
[queue addOperation:op1]; // [op1 start]
[queue addOperation:op2]; // [op2 start]
}
- (void)run1 {
NSLog(@"Thread1 = %@", [NSThread currentThread]);
}
- (void)run2 {
NSLog(@"Thread2 = %@", [NSThread currentThread]);
}
運行結(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)建隊列
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ù)到隊列中
[queue addOperation:op1];
[queue addOperation:op2];
}
運行結(jié)果:Thread1、Thread2、Thread3、Thread4都是子線程,所以添加到隊列之后,是異步的。
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是串行隊列還是并發(fā)隊列
? 可以通過設(shè)置maxConcurrentOperationCount的值來選擇串行隊列還是并發(fā)隊列。
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
/**
maxCount等于-1:默認(rèn)值,也就是不限制最大并發(fā)數(shù),添加的operation都是異步的
maxCount等于0:不執(zhí)行operation
maxCount等于1:在子線程同步執(zhí)行operation,也就是串行隊列
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]);
}];
}
運行結(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è)置op1依賴于op2,op2依賴于op1,會造成死鎖,不會執(zhí)行任務(wù)了
3、任務(wù)可以跨隊列依賴,在不同隊列里面的任務(wù)也可以相互依賴
*/
[op1 addDependency:op2];
[queue addOperation:op1];
[queue addOperation:op2];
}
運行結(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ā)的一個多核編程的較新的解決方法,它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng),它是一個在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù),GCD是一個替代諸如NSThread等技術(shù)的很高效和強大的技術(shù)。
? GCD 會自動利用更多的 CPU 內(nèi)核(比如雙核、四核),GCD 會自動管理線程的生命周期(創(chuàng)建線程、調(diào)度任務(wù)、銷毀線程),程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼。
? GCD兩個核心概念,任務(wù)和隊列,任務(wù)包括(同步執(zhí)行任務(wù)、異步執(zhí)行任務(wù)),隊列包括(串行隊列、并發(fā)隊列),隊列采用 FIFO(First In First Out)的原則,即先進(jìn)先出原則。
1、同步執(zhí)行、異步執(zhí)行
? 同步執(zhí)行與異步執(zhí)行的區(qū)別:是否等待隊列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力。
? 1)同步執(zhí)行(sync):同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行。只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力。
? 2)異步執(zhí)行(async):異步添加任務(wù)到指定的隊列中,它不會做任何等待,可以繼續(xù)執(zhí)行任務(wù)。可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力。
? 注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程。這跟任務(wù)所指定的隊列類型有關(guān)。
2、串行隊列、并發(fā)隊列
? 串行隊列和并發(fā)隊列的區(qū)別:執(zhí)行順序不同,以及開啟線程數(shù)不同。
? 1)串行隊列(Serial Dispatch Queue):讓任務(wù)一個接著一個地執(zhí)行(最多創(chuàng)建一個線程)。dispatch_get_main_queue() 主隊列就是一個串行隊列。
? 2)并發(fā)隊列(Concurrent Dispatch Queue):可以讓多個任務(wù)并發(fā)(同時)執(zhí)行(可以開啟多個線程)。dispatch_get_global_queue(0, 0) 全局隊列就是并發(fā)隊列。
? 注意:并發(fā)隊列 的并發(fā)功能只有在異步(dispatch_async)方法下才有效。
3、經(jīng)典各種組合模式
? 本來同步、異步、串行、并發(fā)有四種組合,但是當(dāng)前代碼默認(rèn)放在主隊列中,全局并發(fā)隊列可以作為普通并發(fā)隊列來使用,所以主隊列很有必要專門來研究一下,所以我們就有六種組合模式了。
? 1)同步執(zhí)行 + 串行隊列:沒有開啟新線程,串行執(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]);
});
}
運行結(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ā)隊列:沒有開啟新線程,串行執(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]);
});
}
運行結(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í)行 + 串行隊列:有開啟新線程(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]);
});
}
運行結(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ā)隊列:有開啟新線程(多條),并發(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]);
});
}
運行結(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í)行+主隊列:死鎖卡住不執(zhí)行。其實如果在串行隊列中嵌套同步使用串行隊列,也會發(fā)生死鎖,原理和這個類似。所以項目中數(shù)據(jù)庫處理FMDataQueue嵌套使用時,需要注意死鎖問題。
? 如果不是在主線程執(zhí)行同步執(zhí)行+主隊列呢,那么不會發(fā)生死鎖(讀者可以自己代碼測試驗證)。
- (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]);
});
}
運行結(jié)果:程序死鎖崩潰,原因是默認(rèn)viewDidLoad被添加到主隊列中(先運行完viewDidLoad,后運行添加的任務(wù)),然后又同步添加Thread1任務(wù)到主隊列中(先運行Thread1任務(wù),后運行viewDidLoad任務(wù)),造成任務(wù)相互等待卡死,程序死鎖崩潰。
? 6)異步執(zhí)行+主隊列:沒有開啟新線程,串行執(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]);
});
}
運行結(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線程間的通信
? 在項目開發(fā)中,一般耗時的操作在別的線程處理,然后在主線程刷新UI,GCD線程間的通信如下:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2); //模擬耗時操作
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
});
}
運行結(jié)果:在子線程執(zhí)行耗時操作,再回到主線程刷新UI。
Thread1 = <NSThread: 0x600002189400>{number = 5, name = (null)}
Thread2 = <NSThread: 0x6000021dc0c0>{number = 1, name = main}
5、GCD其他的常見用法
? 1)柵欄方法(dispatch_barrier_async)
? 我們有時需要異步執(zhí)行兩組操作,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作,這就需要用到dispatch_barrier_async 方法在兩個操作組間形成柵欄。NSOperation的 addDependency 也是這個效果。
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建并發(fā)隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1); //模擬耗時操作
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]);
});
}
運行結(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)延時方法(dispatch_after)
? 項目中我們有時需要幾秒之后再執(zhí)行某個方法,那么可以使用GCD的延時方法,而不用創(chuàng)建一個定時器來處理。
- (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ù)代碼到主隊列,并開始執(zhí)行,如果主線程被阻塞,時間上不會非常準(zhǔn)確
NSLog(@"Thread2 = %@", [NSThread currentThread]);
});
sleep(5); //阻塞主線程
NSLog(@"Thread3 = %@", [NSThread currentThread]);
}
運行結(jié)果:先執(zhí)行Thread1,5s之后執(zhí)行Thread3,當(dāng)前主隊列執(zhí)行完畢,然后執(zhí)行dispatch_after添加的任務(wù)Thread2。至于為什么Thread3、Thread2時間接近,而不是相差2s,因為執(zhí)行完Thread1的2s之后,Thread2添加到主隊列,正在等待主隊列執(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),那么運行結(jié)果:主隊列沒被阻塞,Thread2任務(wù)基本2s之后就會執(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)
? 項目開發(fā)中,我們經(jīng)常使用單例模式,那么就對 dispatch_once 很熟悉,代碼只運行一次。并且即使在多線程的環(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, ^{
// 只會執(zhí)行一次
NSLog(@"Thread3 = %@", [NSThread currentThread]);
});
}
運行結(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)
? 通常我們會用 for 循環(huán)遍歷,但是 GCD 給我們提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次數(shù)將指定的任務(wù)追加到指定的隊列中,并等待全部任務(wù)執(zhí)行結(jié)束。如果是在串行隊列中使用dispatch_apply,那么就和 for 循環(huán)一樣,按順序同步執(zhí)行,所以實際使用的時候一般用并發(fā)隊列。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); //全局并發(fā)隊列
NSLog(@"dispatch_apply begin");
dispatch_apply(5, queue, ^(size_t index) {
sleep(1);
NSLog(@"Thread_%ld = %@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply end");
}
運行結(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)隊列組(dispatch_group)
? 有時候我們會有這樣的需求:分別異步執(zhí)行2個耗時任務(wù),然后當(dāng)2個耗時任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù),這時候我們可以用到 GCD 的隊列組。項目中常見使用場景有:請求多個接口數(shù)據(jù)返回后,再統(tǒng)一進(jìn)行刷新;下載完多張圖片后,再統(tǒng)一進(jìn)行合并繪制等。
? 調(diào)用隊列組的 dispatch_group_async 先把任務(wù)放到隊列中,然后將隊列放入隊列組中?;蛘呤褂藐犃薪M的 dispatch_group_enter、dispatch_group_leave 組合來實現(xiàn) dispatch_group_async。
? 調(diào)用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)?;蛘呤褂?dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會阻塞當(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實現(xiàn)
dispatch_group_enter:標(biāo)志著一個任務(wù)追加到 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) +1
dispatch_group_leave:標(biāo)志著一個任務(wù)離開了 group,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù) -1
當(dāng) group 中未執(zhí)行任務(wù)數(shù)為0的時候,才會使 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會一直等待group結(jié)束,會阻塞當(dāng)前線程,如果設(shè)置為DISPATCH_TIME_NOW就不會等待
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group 執(zhí)行完畢");
}
運行結(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)信號量(dispatch_semaphore)
? 項目開發(fā)中,有時候有這樣的需求:異步執(zhí)行耗時任務(wù),并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通過引入信號量的方式,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks,然后再返回該 tasks。
? GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號。在 Dispatch Semaphore 中,使用計數(shù)來完成這個功能,計數(shù)小于 0 時等待,不可通過。計數(shù)為 0 或大于 0 時,不等待,可通過。信號量主要用于:
? 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;
}
我們自己用信號量實現(xiàn)線程同步:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"dispatch_semaphore begin");
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
/**
創(chuàng)建信號量
小于0:程序閃退
等于0:正常使用,會同步執(zhí)行代碼
大于0:不會同步執(zhí)行
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
/**
dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1
dispatch_semaphore_wait:可以使總信號量減 1,信號總量小于 0 時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
*/
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"Thread1 = %@", [NSThread currentThread]);
dispatch_semaphore_signal(semaphore); //信號量 +1
});
NSLog(@"dispatch_semaphore_wait 之前??");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //信號量 -1
NSLog(@"dispatch_semaphore end");
}
運行結(jié)果:semaphore初始創(chuàng)建時計數(shù)為 0,異步將Thread1任務(wù)添加到全局并發(fā)隊列,不做等待,接著執(zhí)行 dispatch_semaphore_wait 方法,semaphore 減 1,此時 semaphore == -1,當(dāng)前線程進(jìn)入等待狀態(tài)。
? 然后,異步任務(wù) 1 開始執(zhí)行。任務(wù) 1 執(zhí)行到 dispatch_semaphore_signal 之后,總信號量加 1,此時 semaphore == 0,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行,最后打印end。
dispatch_semaphore begin
dispatch_semaphore_wait 之前??
Thread1 = <NSThread: 0x6000021588c0>{number = 4, name = (null)}
dispatch_semaphore end
我們自己用信號量實現(xiàn) semaphore 加鎖:
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 10;
/**
注意:這里參數(shù)是1
信號量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
信號量的初始值為1,代表同時只允許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) { //一直運行,直到break退出
// 等待信號量,類似于加鎖,此時信號量減一,如果信號量減到小于0,那么就等待一個新的信號量發(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]; //模擬耗時操作
} else { //如果已賣完,關(guān)閉售票窗口
NSLog(@"火車票售完");
// 發(fā)送一個信號量,類似于解鎖
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
// 發(fā)送一個信號量,類似于解鎖
dispatch_semaphore_signal(self.semaphoreLock);
}
}
運行結(jié)果:ticketCount 按照順序依次遞減1,“火車票售完”打印三次是因為有三條線程運行。
? 分析過程:首先三條線程都并發(fā)執(zhí)行sellTickets方法,最快的一條線程首先執(zhí)行dispatch_semaphore_wait,信號量減一,此時信號量為0,該條線程繼續(xù)執(zhí)行wait之后的代碼,而其他較慢的兩條線程進(jìn)行等待新的信號量出現(xiàn),較快的一條線程賣票之后,發(fā)送一個信號量,那么較慢的兩條線程其中的一條執(zhí)行wait之后的代碼,如此循環(huá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)}
火車票售完
火車票售完
火車票售完
如果把信號量的代碼注釋,運行結(jié)果:剩余票數(shù)順序明顯不對,數(shù)據(jù)錯亂。
剩余票數(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)多個線程訪問同一塊資源時,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題。線程安全問題:一般使用線程同步技術(shù),同步技術(shù)有加鎖、串行隊列、信號量等。
? 串行隊列,上文已經(jīng)有介紹,比如項目中FMDBQueue,就是在串行隊列中同步操作數(shù)據(jù)庫,從而保證線程安全。
? 信號量,上文已經(jīng)有介紹,其實類似于互斥鎖。
? 下面主要講iOS中常見的鎖:
? 從大的方向講有兩種鎖:自旋鎖、互斥鎖。
? 自旋鎖:線程反復(fù)檢查鎖變量是否可用,是一種忙等狀態(tài),自旋鎖避免了進(jìn)程上下文的調(diào)度開銷,因此對于線程只會阻塞很短時間的場合是有效的。自旋鎖的性能高于互斥鎖,因為響應(yīng)速度快,但是可能發(fā)生優(yōu)先級反轉(zhuǎn)問題(如果等待鎖的線程優(yōu)先級較高,它會一直占用著CPU資源,優(yōu)先級低的線程就無法釋放鎖)。常見的自旋鎖包括:OSSpinLock、atomic等。
? 互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了,線程會進(jìn)入休眠狀態(tài)等待鎖,因此適用于線程阻塞時間較長的場合。常見的互斥鎖包括:os_unfair_lock、pthread_mutex、dispatch_semaphore、@synchronized,其中pthread_mutex又衍生出NSLock、NSCondition、NSConditionLock、NSRecursiveLock,更加面向?qū)ο蟮逆i。
至于鎖的性能參考這個博客傳送門,我只把測試結(jié)果放出來。

1、OSSpinLock
? 自旋鎖OSSpinLock,已經(jīng)在iOS10被蘋果棄用,因為它存在優(yōu)先級反轉(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)行對比學(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的各種同步機制,感興趣的可以看看這篇文章pthread的各種同步機制,可謂講的相當(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ū)ο?,使用也很簡單?/p>
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è)置線程等待時間,過了這個時間就會自動執(zhí)行后面的代碼
- (void)signal; // 喚醒一個設(shè)置為wait等待的線程
- (void)broadcast; // 喚醒所有設(shè)置為wait等待的線程
- (void)viewDidLoad {
[super viewDidLoad];
// 比如刪除數(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ā)送信號
});
}
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時,嘗試加鎖
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];
});
}
運行結(jié)果:condition初始化是0,所以先加鎖執(zhí)行Thread1,然后解鎖并且設(shè)置condition為1;接著當(dāng)condition為1時,先加鎖執(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
? 遞歸鎖,遞歸鎖有一個特點,就是同一個線程可以加鎖N次而不會引發(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é)束時銷毀鎖
6、pthread_rwlock
? 讀寫鎖,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,可以保證讀寫的正確性。如果需要實現(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,雖然在所有鎖里面性能最差,但是代碼最簡單,所以項目實際使用很常見。
@synchronized (self) {
// [self sellTickets];
}
總結(jié)一下:
? 1)大部分鎖都是先創(chuàng)建鎖,然后加鎖、解鎖等流程;
? 2)如果對性能有要求,優(yōu)先使用dispatch_semaphore或者pthread_mutex,如果對性能無要求,一般使用最簡單的@synchronized即可。