原創(chuàng)承边,轉(zhuǎn)載請注明出處聊训。
拋磚引玉彤悔。最近在復(fù)習(xí)了《Obj-C高級編程》這本書后乡翅,一方面記錄一下知識點,另一方便加了一些自己的理解瞬浓。結(jié)合一些經(jīng)典的例子以及實際使用場景加深理解旋讹,權(quán)當(dāng)學(xué)習(xí)交流之用序厉。
需要了解的基本概念
1.同步執(zhí)行:阻塞當(dāng)前線程邻辉。
2.異步執(zhí)行:不阻塞當(dāng)前線程溪王。
3.串行隊列:按照FIFO原則出列腮鞍,一個一個的執(zhí)行。
4.并行隊列: 一起執(zhí)行莹菱。
后續(xù)內(nèi)容會再做解釋移国。
基礎(chǔ)API
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
這是gcd 中最常見的兩個API,其中:
參數(shù)部分
- 第一個參數(shù)
dispatch_queue_t
代表放在哪個隊列執(zhí)行道伟,系統(tǒng)提供了幾種隊列:
-
dispatch_get_global_queue(long identifier, unsigned long flags)
全局并行隊列桥狡。第一個參數(shù)表示優(yōu)先級,最后一個參數(shù)寫0就可以了皱卓。系統(tǒng)提供了四種優(yōu)先級:(優(yōu)先級由高到低)
* - DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
* - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
* - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
* - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
一般dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
這么寫就行了。
-
dispatch_get_main_queue()
主隊列部逮,也就是主線程的執(zhí)行隊列娜汁。此為串行隊列。按照FIFO原則執(zhí)行兄朋。 - 同樣我們也可以自定義隊列:
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
第一個參數(shù)為隊列的名稱掐禁,《Obj-C高級編程》作者推薦使用應(yīng)用ID這種逆序全程域名的命名方式:"com.example.gcd.MyConcurrentDispatchQueue",該名稱會出現(xiàn)在程序崩潰的crashlog中颅和。便于排查問題傅事。
第二個參數(shù)代表隊列類型,NULL
或DISPATCH_QUEUE_SERIAL
創(chuàng)建串行隊列峡扩。DISPATCH_QUEUE_CONCURRENT
創(chuàng)建并行隊列蹭越。
- 第二個參數(shù)
dispatch_block_t
是一個block,我們把需要使用GCD執(zhí)行的任務(wù)放在這個block里教届。
函數(shù)部分
dispatch_sync
:代表同步執(zhí)行响鹃,對應(yīng)基礎(chǔ)概念里的同步執(zhí)行。
這個方法會阻塞當(dāng)前線程案训,將第二個參數(shù)block里的任務(wù)追加到第一個參數(shù)指定的隊列queue里執(zhí)行买置。直到block里的任務(wù)執(zhí)行完畢,程序才繼續(xù)往下運行强霎。
假如當(dāng)前線程的執(zhí)行隊列和第一個參數(shù)里的queue是同一個隊列忿项,且都是串行隊列,那么就會造成死鎖城舞。(見代碼1.1.1,1.1.2)
dispatch_async
:代表異步執(zhí)行轩触。不阻塞當(dāng)前線程,即使用多個線程同時執(zhí)行多個處理椿争。其中異步執(zhí)行一個并行隊列怕膛,XNU內(nèi)核會基于Dispatch Queue中的處理數(shù)、CPU核數(shù)以及CPU負荷等當(dāng)前系統(tǒng)的狀態(tài)來決定要不要開辟新的線程秦踪,以及開辟多少個線程來處理褐捻。(代碼1.2)
代碼 1.1.1 死鎖
當(dāng)前線程為主線程掸茅,當(dāng)前隊列為主隊列。queue內(nèi)參數(shù)也為主隊列柠逞,同時他們都是串行隊列昧狮。那么按照剛剛我們總結(jié)的簡單理論,發(fā)生死鎖板壮。
NSLog(@"程序開始運行");
//主線程阻塞,開始執(zhí)行block里的任務(wù)
dispatch_sync(dispatch_get_main_queue(), ^{
//task2
NSLog(@"此句不執(zhí)行,加到主隊列中執(zhí)行,排在task3后面逗鸣,按照FIFO原則需要等待 task3執(zhí)行完畢才能執(zhí)行");
});
// task2 等待task3, task3 等待 task2 .死鎖
NSLog(@"此句不執(zhí)行,主線程主隊列死鎖");
我們將dispatch_get_main_queue
替換為dispatch_get_global_queue
使得二者不為同一個串行隊列,則不會發(fā)生死鎖绰精。同學(xué)們可以自行試驗撒璧。
代碼 1.1.2 死鎖 。
當(dāng)前執(zhí)行隊列和dispatch_sync
queue參數(shù)都為同一個同步隊列笨使。發(fā)生死鎖卿樱。
NSLog(@"task1");
dispatch_queue_t otherQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(otherQueue, ^{
NSLog(@"task2");
//發(fā)生死鎖,當(dāng)前執(zhí)行隊列和dispatch_sync 執(zhí)行隊列相同且都是同步隊列
dispatch_sync(otherQueue, ^{
NSLog(@"task4");
//task4 排在otherQueue執(zhí)行任務(wù)task5之后硫椰,需要task5執(zhí)行完畢才可以執(zhí)行繁调。
});
//同步執(zhí)行,需要task4 執(zhí)行完畢才可以執(zhí)行task5靶草。
//task4 等待task5, task5 等待task4,發(fā)生死鎖蹄胰。
NSLog(@"task5");
});
//打印 task1 task2 task3(task2,task3順序不定)
NSLog(@"task3");
同樣我們可以將dispatch_sync
里的otherQueue替換為任意一個非相同隊列,則不會發(fā)生死鎖奕翔。這里也不再贅述裕寨。
代碼 1.2 是否開啟新線程,以及開辟多少個。
dispatch_queue_t queue1 = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("com.test.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
NSLog(@"thread%@",[NSThread currentThread]);
dispatch_async(queue2, ^{
NSLog(@"thread%@",[NSThread currentThread]);
});
});
//有時打印
//thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
//thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
//有時打印
//thread<NSThread: 0x6000037a1f40>{number = 4, name = (null)}
//thread<NSThread: 0x600003751e40>{number = 6, name = (null)}
以上試驗也可以驗證了這一理論糠悯,有時只需要開辟一個線程即可處理帮坚。有時需要開辟兩個新線程。
iOS和OS X的核心--XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù)互艾,并只生成所需的線程執(zhí)行處理试和。另外,當(dāng)處理結(jié)束纫普,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時阅悍,XNU內(nèi)核會結(jié)束不再需要的線程。XNU內(nèi)核僅使用Concurrent Dispatch Queue便可完美地管理并行執(zhí)行多個處理的線程昨稼。
dispatch_set_target_queue
dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue);
此API节视,可以改變執(zhí)行隊列優(yōu)先級以及隊列類型。
- Concurrent Dispatch Queue 改 Serial Dispatch Queue :
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(concurrentQueue, serialQueue);
dispatch_async(concurrentQueue, ^{
NSLog(@"task1 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task2 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task3 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task4 thread:%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"task5 thread:%@",[NSThread currentThread]);
});
注釋掉
dispatch_set_target_queue
這行假栓,會無序打印task1-5寻行。
加上后實際上執(zhí)行隊列由并行變成了串行執(zhí)行,task1-5按順序打印匾荆。
- 改變隊列優(yōu)先級
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(concurrentQueue, globalQueue);
dispatch_queue_create
函數(shù)生成的Dispatch Queue 不管是Serial Queue 還是 Concurrent Queue拌蜘,都使用與默認優(yōu)先級Global Dispatch Queue相同執(zhí)行優(yōu)先級的線程杆烁。而變更生成的Dispatch Queue 的執(zhí)行優(yōu)先級要使用dispatch_set_target_queue
函數(shù)。
dispatch_after
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
參數(shù):
1.時間(指定時間追加處理到Dispatch Queue)简卧。
2.隊列兔魂。
3.任務(wù)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull *NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"3秒后追加到主線程隊列里執(zhí)行");
});
因為主線程主隊列举娩,在主線程RunLoop中執(zhí)行析校,所以在每隔1/60秒執(zhí)行的RunLoop中,任務(wù)最快在3秒后執(zhí)行铜涉,最慢在3+1/60秒后執(zhí)行智玻。如果主隊列有大量處理,那么這個時間會更長芙代。
Dispatch Group
在多個并行執(zhí)行的任務(wù)全部執(zhí)行完畢后尚困,想要追加一個結(jié)束處理。這種場景往往比較常見链蕊。雖然可以通過別的方式實現(xiàn),但邏輯會變的復(fù)雜谬泌,代碼也不雅觀滔韵。這時候Dispatch Group
就發(fā)揮作用了。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"task Done");
});
//打印 (task1-3 無序)
//task1
//task2
//task3
//task Done
一個簡單的demo掌实,在任務(wù)1-3完成后陪蜻,執(zhí)行task Done
除了使用dispatch_group_notify
API 處理group任務(wù)結(jié)束外,還可以使用dispatch_group_wait
函數(shù)贱鼻。同樣的例子:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
});
//也可以使用dispatch_group_wait 函數(shù)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"task Done");
DISPATCH_TIME_FOREVER
代表永久等待宴卖。
我們也可以指定等待的時間,下例等待1秒邻悬,超過1秒不再等待症昏。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"task1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task3");
for (int i = 0; i < 10000000; i++){
@autoreleasepool{
NSString* string = @"ab c";
//生成autorelease對象
NSArray* array = [string componentsSeparatedByString:string];
}
}
});
//DISPATCH_TIME_FOREVER 永久等待,同樣我們可以設(shè)置等待的時間
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
// 屬于Dispatch Group 的全部處理執(zhí)行結(jié)束
NSLog(@"task Done");
} else {
// 屬于Dispatch Group 的某個處理還在執(zhí)行中
NSLog(@"task Doing");
}
通過
dispatch_group_wait
返回值可以判斷父丰,是group任務(wù)在設(shè)置的超時時間內(nèi)完成肝谭,還是超時未完成。 result ==0 代表全部處理完成蛾扇,非0代表執(zhí)行超時了攘烛。
但假如Dispatch Queue 里的任務(wù)是一個個網(wǎng)絡(luò)請求的話,由于網(wǎng)絡(luò)請求是異步執(zhí)行镀首,那么實際達不到我們想要的在所有請求完畢后執(zhí)行某段代碼的目的坟漱。那么這時就可以借助信號量Dispatch Semaphore
來完成了。
Dispatch Semaphore
- dispatch_semaphore_create(long value) 創(chuàng)建一個信號量更哄。
- dispatch_semaphore_signal(dispatch_semaphore_t dsema) 信號量加1
- dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout) 等待信號大0時執(zhí)行芋齿,并對信號量進行減一操作腥寇。
1.上述在Dispatch Queue里并行執(zhí)行多個網(wǎng)絡(luò)請求的情況,想要在所有請求都完成的情況執(zhí)行某段代碼就可以使用Dispatch Semaphore了沟突。
- (void)requestDemo{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__weak typeof(self) weak_self = self;
dispatch_group_async(group, queue, ^{
NSLog(@"請求任務(wù)A");
[weak_self requestA];
});
dispatch_group_async(group, queue, ^{
NSLog(@"請求任務(wù)B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"請求任務(wù)C");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"所有請求完成");
});
}
- (void)requestA{
// 用于GCD Group 以及 NSOperationQueue中設(shè)置依賴關(guān)系的任務(wù)花颗,因為網(wǎng)絡(luò)請求異步執(zhí)行,
// 不會阻塞當(dāng)前線程惠拭,達不到按序執(zhí)行的效果扩劝。
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// [異步請求:{
// 成功: dispatch_semaphore_signal(sema);
// 失敗: dispatch_semaphore_signal(sema);
// }];
//一直等待到信號量大于0才執(zhí)行,并減1
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
異步請求方法前創(chuàng)建為0的信號量职辅,請求結(jié)束后信號量+1棒呛,
dispatch_semaphore_wait
會等到信號量大于才繼續(xù)運行。整個請求模塊會在dispatch_semaphore_wait
可以繼續(xù)運行才標記為block任務(wù)結(jié)束域携。
2.控制異步執(zhí)行Dispatch Concurrent Queue最大并發(fā)數(shù)簇秒。
眾所周知NSOperationQueue
便于管理多線程,可以設(shè)置maxConcurrentOperationCount
來控制多線程執(zhí)行的最大并發(fā)數(shù)秀鞭。那么GCD要如何控制最大并發(fā)呢趋观?這時Dispatch Semaphore又發(fā)揮作用了。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//初始信號量1 锋边,這里1可以為n
dispatch_semaphore_t sema = dispatch_semaphore_create(1);
for (NSInteger i = 0; i < 10; i++) {
//大于0執(zhí)行皱坛,并減1
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"%ld",i);
//任務(wù)完成,信號量加1
dispatch_semaphore_signal(sema);
});
}
//按順序打印0-9
通過設(shè)置
dispatch_semaphore_create (1)
設(shè)置最大并發(fā)1豆巨,那么實際上就把并發(fā)隊列設(shè)置成了一個串行隊列剩辟。dispatch_semaphore_create (n)
則最大并發(fā)為n,如果n設(shè)置的很大往扔,實際上達不到n贩猎。因為蘋果內(nèi)核決定了此次GCD執(zhí)行的并發(fā)隊列所需要的線程數(shù)。
未完待續(xù)萍膛。吭服。。
后續(xù)補充:
dispatch_barrier_async
dispatch_apply
dispatch_once