- pthread
- NSThread
- GCD
1. 同步、異步、并發(fā)、串行講解
2. 創(chuàng)建隊列的幾種方式
3. 柵欄函數(shù)
4. 隊列組
5. GCD快速迭代 - NSOperation和NSOperationQueue
1. NSInvocationOperation和NSBlockOperation
2. NSOperationQueue
3. 任務依賴 - GCD和NSOperation的比較
- 多線程的安全隱患
關(guān)于多線程巷查,在 iOS 中目前有 4 套方案,他們分別是:
下面我們分別來為大家一一介紹上述方案:
方案一:pthread
#import <pthread.h>
//創(chuàng)建線程對象
pthread_t thread = NULL;
//傳遞的參數(shù)
id str = @"i'm pthread param";
//創(chuàng)建線程
/* 參數(shù)一:線程對象 傳遞線程對象的地址
參數(shù)二:線程屬性 包括線程的優(yōu)先級等
參數(shù)三:子線程需要執(zhí)行的方法
參數(shù)四:需要傳遞的參數(shù)
*/
int result = pthread_create(&thread, NULL, operate, (__bridge void *)(str));
if (result == 0) {
NSLog(@"創(chuàng)建線程 OK");
} else {
NSLog(@"創(chuàng)建線程失敗 %d", result);
}
//手動把當前線程結(jié)束掉
// pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運行結(jié)束后會自動釋放所有資源抹腿。
pthread_detach(thread);
void *operate(void *params){
NSString *str = (__bridge NSString *)(params);
NSLog(@"%@ - %@", [NSThread currentThread], str);
return NULL;
}
方案二:NSThread
- 先創(chuàng)建線程類岛请,再啟動
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動
[thread start];
- 創(chuàng)建后立即啟動
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
方案三:GCD
它是蘋果為多核的并行運算提出的解決方案,所以它會自動合理地利用更多的CPU內(nèi)核警绩,最重要的是它會自動管理線程的生命周期(比如創(chuàng)建線程崇败、調(diào)度任務、銷毀線程)
1. 同步房蝉、異步僚匆、并發(fā)、串行講解
GCD中有2個用來執(zhí)行任務的函數(shù)
用同步的方式執(zhí)行任務
//queue:隊列 block:任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
用異步的方式執(zhí)行任務
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
容易混淆的術(shù)語
有4個術(shù)語比較容易混淆:同步
搭幻、異步
咧擂、并發(fā)
、串行
同步和異步主要影響:能不能開啟新的線程
同步:在當前線程中執(zhí)行任務檀蹋,不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務松申,具備開啟新線程的能力(但是不一定能夠開啟新線程,比如異步在主隊列中執(zhí)行任務)
并發(fā)和串行主要影響:任務的執(zhí)行方式
并發(fā):多個任務并發(fā)(同時)執(zhí)行
串行:一個任務執(zhí)行完畢后,再執(zhí)行下一個任務
各種隊列的執(zhí)行效果
注意:使用sync函數(shù)往當前串行隊列中添加任務俯逾,會卡住當前的串行隊列(產(chǎn)生死鎖)
2. 創(chuàng)建隊列的幾種方式
-
主隊列: 它是一個特殊的
串行隊列
, 任何需要刷新 UI 的工作都要在主隊列執(zhí)行贸桶。
dispatch_queue_t queue = dispatch_get_main_queue();
-
自定義隊列: 自己可以創(chuàng)建
串行隊列
, 也可以創(chuàng)建并行隊列
。
//串行隊列
dispatch_queue_t queue = dispatch_queue_create("test1", NULL);
dispatch_queue_t queue = dispatch_queue_create("test2", DISPATCH_QUEUE_SERIAL);
//并行隊列
dispatch_queue_t queue = dispatch_queue_create("test3", DISPATCH_QUEUE_CONCURRENT);
- 全局并行隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 驗證一下所學知識
下面我們來看看下面幾段代碼桌肴,猜一猜運行之后結(jié)果是個啥子嘛皇筛。。坠七。
考題一:
NSLog(@"任務一");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務二");
});
NSLog(@"任務三");
額水醋。旗笔。。知道結(jié)果了么拄踪?下面我們來揭曉答案
執(zhí)行結(jié)果:
為什么會這樣呢?
因為同步任務會阻塞當前線程蝇恶,然后把 Block 中的任務放到主隊列中執(zhí)行,隊列是FIFO
,所以Block中的任務只有等到dispatch_sync
執(zhí)行完畢后才會執(zhí)行,但是dispatch_sync
要想執(zhí)行完成必須Block中的任務執(zhí)行完畢后才會結(jié)束.這就是非常經(jīng)典的死鎖現(xiàn)象.
考題二:
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"任務一");
dispatch_async(queue, ^{
NSLog(@"任務二");
dispatch_sync(queue, ^{
NSLog(@"任務三");
});
NSLog(@"任務四");
});
NSLog(@"任務五");
看了考題一的分析 我相信考題二難不住你的惶桐,我們來看看打印結(jié)果:
其實原因跟上一個例子我們分析的原因類似,記住這句結(jié)論就好:
注意:使用sync函數(shù)往當前串行隊列中添加任務撮弧,會卡住當前的串行隊列(產(chǎn)生死鎖)
3. 柵欄函數(shù)
在項目中有很多場景需要控制任務的執(zhí)行順序,比如需要等任務A, 任務B, 任務C都完成后(其中A, B, C沒有順序要求), 才進行下一步的處理任務, 可以使用 dispatch_group很方便的完成 (也可以使用柵欄函數(shù))
如果上面的A, B, C任務順序也有順序要求呢? 必須A任務完成后, 才能進行B任務, B完成后才進行C任務, 這時我們就需要用到柵欄函數(shù)
dispatch_barrier_async
:在進程管理中起到一個柵欄
的作用,該函數(shù)需要同dispatch_queue_create
函數(shù)生成的并發(fā)隊列
一起使用才能生效姚糊。
第一種情況
A, B, C任務完成之后(A, B, C無順序要求), 進行任務D
1.使用dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"開始任務A");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務A done.");
});
dispatch_async(queue, ^{
NSLog(@"開始任務B");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務B done.");
});
dispatch_async(queue, ^{
NSLog(@"開始任務C");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任務C done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"----------> barrier <----------");
});
dispatch_async(queue, ^{
NSLog(@"開始任務D");
});
打印結(jié)果:
開始任務C
開始任務A
開始任務B
任務C done.
任務B done.
任務A done.
----------> barrier <----------
開始任務D
可以看出在執(zhí)行完柵欄前面的操作之后才執(zhí)行柵欄操作贿衍,然后再執(zhí)行柵欄后邊的操作。
如果不加barrier 函數(shù), 輸出如下:
開始任務B
開始任務A
開始任務D
開始任務C
任務C done.
任務B done.
任務A done.
dispatch_barrier_async和dispatch_barrier_sync使用區(qū)別:
dispatch_barrier_async和dispatch_barrier_sync是 GCD 中的兩個方法叛拷。是不是和dispatch_async及dispatch_sync長得很像舌厨,就是多了一個barrier(譯:柵欄)岂却。
沒錯忿薇,除了有dispatch_async或dispatch_sync的作用外(是否阻塞當前線程),還有“柵欄”的效果躏哩。
意思就是署浩,在該隊列,以他們?yōu)榻缟ǔ撸懊嫒蝿請?zhí)行完成筋栋,再把自己內(nèi)部的任務執(zhí)行完,才會執(zhí)行后面的任務正驻。
知道和dispatch_async及dispatch_sync對應弊攘,就應該想到:
dispatch_barrier_async不阻塞當前線程,dispatch_barrier_async里面的任務異步執(zhí)行姑曙。
dispatch_barrier_sync會阻塞當前線程襟交,dispatch_barrier_sync里面的任務同步執(zhí)行。
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);
//以下任務
dispatch_async(concurrentQueue, ^{ NSLog(@"任務1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務2"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務3"); });
dispatch_barrier_async(concurrentQueue, ^{
sleep(1);
NSLog(@"I am barrier");
});
NSLog(@"當前線程");
dispatch_async(concurrentQueue, ^{ NSLog(@"任務4"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務5"); });
輸出結(jié)果:
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);
//以下任務
dispatch_async(concurrentQueue, ^{ NSLog(@"任務1"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務2"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務3"); });
dispatch_barrier_sync(concurrentQueue, ^{
sleep(1);
NSLog(@"I am barrier");
});
NSLog(@"當前線程");
dispatch_async(concurrentQueue, ^{ NSLog(@"任務4"); });
dispatch_async(concurrentQueue, ^{ NSLog(@"任務5"); });
輸出結(jié)果
2.使用 dispatch_group
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務A");
[NSThread sleepForTimeInterval:3];
NSLog(@"任務A done.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務B");
[NSThread sleepForTimeInterval:2];
NSLog(@"任務B done.");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"開始任務C");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務C done.");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"開始任務D");
});
輸出如下:
開始任務C
開始任務B
開始任務A
任務C done.
任務B done.
任務A done.
開始任務D
第二種情況伤靠,任務依賴
A, B, C任務完成之后(A, B, C順序要求依次執(zhí)行), 進行任務D
dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務A");
[NSThread sleepForTimeInterval:1];
NSLog(@"任務A done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務B");
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務B done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務C");
[NSThread sleepForTimeInterval:0.2];
NSLog(@"任務C done.");
});
dispatch_barrier_async(queue, ^{
NSLog(@"開始任務D");
});
輸出如下:
開始任務A
任務A done.
開始任務B
任務B done.
開始任務C
任務C done.
開始任務D
在每個網(wǎng)絡請求開始前使用
dispatch_group_enter
來進行標識捣域,網(wǎng)絡請求有回調(diào)后使用dispatch_group_leave
來進行標識,這樣就能保證group_notify
在所有網(wǎng)絡請求都有回調(diào)之后才調(diào)用
5. GCD快速迭代
我們知道for循環(huán)
中的代碼是串行執(zhí)行的宴合,如果此時我們有一系列的耗時操作需要執(zhí)行焕梅,此時我們可以使用Dispatch_apply
函數(shù),他可以異步執(zhí)行卦洽,同時可以利用多核優(yōu)勢贞言,完美替代for循環(huán)。
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd = %@",index,[NSThread currentThread]);
});
執(zhí)行結(jié)果如下:
可以看到上述循環(huán)是在多個線程中并發(fā)執(zhí)行的阀蒂。
6. 考題:猜測打印結(jié)果
考題一:
- (void)test{
NSLog(@"任務B");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務A");
[self performSelector:@selector(test) withObject:nil afterDelay:1.0];
NSLog(@"任務C");
});
}
知道結(jié)果了么?
我們來看看打印結(jié)果:
為什么只輸出了任務A和任務C而沒有任務B呢?其實這里涉及到了
RunLoop的知識
,因為performSelector:withObject:afterDelay:
的本質(zhì)是向RunLoop
中添加定時器,而子線程中默認是沒有開啟RunLoop
的,所以這里我們需要稍微改動下代碼,如下;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務A");
[self performSelector:@selector(hahha) withObject:nil afterDelay:1.0];
NSLog(@"任務C");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
關(guān)于RunLoop
有興趣的朋友可以看看我的這篇文章: RunLoop的使用
考題二:
- (void)test{
NSLog(@"任務B");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任務A");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
執(zhí)行結(jié)果:
[73860:11959832] 任務A
[73860:11959410] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
因為我們在執(zhí)行完[thread start];
的時候執(zhí)行任務A,此時線程就被銷毀了,如果我們要在thread
線程中執(zhí)行test
方法需要保住該線程的命,即線程备么埃活
,代碼需要修改如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任務A");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
方案四:NSOperation和NSOperationQueue
NSOperation
是蘋果公司對 GCD面向?qū)ο蟮姆庋b打肝,所以使用起來非常方便。
NSOperation
和NSOperationQueue
分別對應 GCD 的 任務 和 隊列 挪捕。
使用步驟大致如下:
- 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
- 然后將NSOperation對象添加到NSOperationQueue中
- 系統(tǒng)會?動將NSOperationQueue中的NSOperation取出來
- 將取出的NSOperation封裝的操作放到?條新線程中執(zhí)?
1. 任務
NSOperation
只是一個抽象類粗梭,所以不能封裝任務。
但是我們可以使用它的兩個子類對象:NSInvocationOperation
级零、NSBlockOperation
断医。
- NSInvocationOperation : 需要傳入一個方法名。
//1.創(chuàng)建NSInvocationOperation對象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開始執(zhí)行
[operation start];
打印結(jié)果:
其實等價于[self run];
在主線程中執(zhí)行奏纪。
如果我們想讓任務在子線程中執(zhí)行鉴嗤,我們需要創(chuàng)建一個NSOperationQueue
,如下:
// 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 創(chuàng)建操作
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 添加操作到隊列中序调,會自動異步執(zhí)行
[queue addOperation:operation];
打印結(jié)果:
注意:操作對象默認在主線程中執(zhí)行醉锅,只有將NSOperation
放到一個 NSOperationQueue
中,才會異步執(zhí)行操作
- NSBlockOperation:用來并發(fā)的執(zhí)行一個或者多個Block對象发绢。
注意:addExecutionBlock:
該方法只要NSBlockOperation封裝的操作數(shù) > 1笋婿,就會異步執(zhí)行操作
//1.創(chuàng)建NSBlockOperation對象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開始任務
[operation start];
打印結(jié)果:
<NSThread: 0x604000074780>{number = 1, name = main}
addExecutionBlock方式添加多個任務:
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
//---下載圖片----1---<NSThread: 0x600000260fc0>{number = 1, name = main}
NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
}];//這種方式只有第一個是主線程,其余都是子線程
[operation addExecutionBlock:^{
//---下載圖片----2---<NSThread: 0x600000263f40>{number = 3, name = (null)}
NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
//---下載圖片----3---<NSThread: 0x60800026c440>{number = 4, name = (null)}
NSLog(@"---下載圖片----3---%@", [NSThread currentThread]);
}];
[operation start];
2. 隊列
通過上面的介紹我們知道調(diào)用NSOperation
對象的start()
方法可以啟動任務赶袄,但是這樣做他們默認是 同步執(zhí)行 的挟憔。即使是addExecutionBlock
方法,也會在 當前線程和其他線程 中執(zhí)行墩朦,也就是說還是會占用當前線程坯认。此時我們就需要用到NSOperationQueue
了。
只要任務添加到隊列氓涣,便會自動調(diào)用任務的start()
方法
//1.創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
}];
[operation addExecutionBlock:^{
NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
}];
// 2.添加操作到隊列中(自動異步執(zhí)行)
[queue addOperation:operation];
打印結(jié)果:
任務依賴
需求:此時有 3 個任務牛哺,這三個任務因為比較耗時,所以需要異步并發(fā)執(zhí)行劳吠。
任務一: 從服務器上下載一張圖片
任務二:給這張圖片加個水印
任務三:把圖片返回給服務器引润。
這時候就需要控制任務的執(zhí)行順序了
//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.設(shè)置依賴
[operation2 addDependency:operation1]; //任務二依賴任務一
[operation3 addDependency:operation2]; //任務三依賴任務二
//5.創(chuàng)建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
打印結(jié)果
- 注意:不能添加相互依賴,比如 A依賴B赴背,B又依賴A椰拒,否則會造成死鎖
1. 從其他線程回到主線程的方法
我們都知道在其他線程操作完成后必須到主線程更新UI。所以凰荚,介紹完所有的多線程方案后燃观,我們來看看有哪些方法可以回到主線程。
- NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
- GCD
dispatch_async(dispatch_get_main_queue(), ^{
});
- NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
2. 延遲執(zhí)行方案
公用延遲執(zhí)行方法
- (void)delayMethod{
NSLog(@"delayMethodEnd");
}
線程阻塞式
1.NSThread線程的sleep
[NSThread sleepForTimeInterval:2.0];
此方法是一種阻塞執(zhí)行方式便瑟,建議放在子線程中執(zhí)行缆毁,否則會卡住界面。但有時還是需要阻塞執(zhí)行到涂,比如進入歡迎界面需要沉睡2秒才進入主界面時脊框。
非阻塞執(zhí)行方式
performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:2.0];
NSTimer定時器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
-
GCD
的方式
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
[weakSelf delayMethod];
});
此方法可以在參數(shù)中選擇執(zhí)行的線程颁督,是一種非阻塞執(zhí)行方式
GCD和NSOperation的比較
GCD 和 NSOperation的區(qū)別主要表現(xiàn)在以下幾方面:
1、GCD是一套 C 語言API,執(zhí)行和操作簡單高效浇雹,NSOperation底層也通過GCD實現(xiàn)沉御,這是他們之間最本質(zhì)的區(qū)別,因此如果希望自定義任務昭灵,建議使用NSOperation吠裆;
2、依賴關(guān)系烂完,NSOpeartion可以通過addDependency來添加任務的依賴试疙,GCD需要添加依賴只能通過dispatch_barrier_async
3、KVO(鍵值對觀察)抠蚣,可以監(jiān)測operation是否正在執(zhí)行(isExecuted)祝旷、是否結(jié)束(isFinished),是否取消(isCanceld)對此GCD無法通過KVO進行判斷嘶窄;
4怀跛、優(yōu)先級,NSOpeartion可以設(shè)置queuePriority來設(shè)置優(yōu)先級护侮,跳轉(zhuǎn)任務的執(zhí)行先后順序敌完,GCD只能設(shè)置隊列的優(yōu)先級,且任務是根據(jù)先進先出FIFO的原則來執(zhí)行的羊初,不能設(shè)置任務的優(yōu)先級。
5什湘、繼承长赞,NSOperation是一個抽象類。實際開發(fā)中常用的是它的兩個子類:NSInvocationOperation和NSBlockOperation闽撤,同樣我們可以自定義NSOperation得哆,GCD執(zhí)行任務可以自由組裝,沒有繼承那么高的代碼復用度哟旗;
6贩据、效率,直接使用GCD效率確實會更高效闸餐,NSOperation會多一點開銷饱亮,但是通過NSOperation可以獲得依賴,優(yōu)先級舍沙,繼承近上,鍵值對觀察這些優(yōu)勢,相對于多的那么一點開銷確實很劃算拂铡,魚和熊掌不可得兼壹无,取舍在于開發(fā)者自己葱绒;
7、NSOperation可以設(shè)置暫停斗锭,掛起等操作地淀,可以隨時取消準備執(zhí)行的任務(已經(jīng)在執(zhí)行的不能取消),GCD沒法停止已經(jīng)加入queue 的 block(雖然也能實現(xiàn),但是需要很復雜的代碼)
基于GCD簡單高效,更強的執(zhí)行能力岖是,操作不太復雜的時候,優(yōu)先選用GCD;而比較復雜的任務可以自己通過NSOperation實現(xiàn)骚秦。
8、NSOperation可以設(shè)置最大任務數(shù)璧微,
多線程的安全隱患
當多個線程同時訪問同一個資源時作箍,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,比如下圖:
那么我們該如何去解決這個問題呢前硫?
我們可以使用線程同步技術(shù)
胞得。所謂同步,就是協(xié)同步調(diào)屹电,按預定的先后次序進行阶剑。常見的線程同步技術(shù)就是加鎖
關(guān)于鎖的實現(xiàn)方案 網(wǎng)上有很多,這里我就不再列舉了危号,可以參考:
iOS中保證線程安全的幾種方式與性能對比
iOS 常見知識點(三):Lock
深入理解iOS開發(fā)中的鎖