一材鹦,GCD執(zhí)行原理
1.GCD有一個(gè)底層線程池,這個(gè)池中存放的是一個(gè)個(gè)的線程耕姊。之所以稱為“池”桶唐,很容易理解出這個(gè)“池”中的線程是可以重用的,當(dāng)一段時(shí)間后這個(gè)線程沒(méi)有被調(diào)用胡話茉兰,這個(gè)線程就會(huì)被銷毀尤泽。注意:開(kāi)多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統(tǒng)自動(dòng)來(lái)維護(hù),不需要我們程序員來(lái)維護(hù)(看到這句話是不是很開(kāi)心坯约?) 而我們程序員需要關(guān)心的是什么呢熊咽?我們只關(guān)心的是向隊(duì)列中添加任務(wù),隊(duì)列調(diào)度即可闹丐。
2.如果隊(duì)列中存放的是同步任務(wù)横殴,則任務(wù)出隊(duì)后,底層線程池中會(huì)提供一條線程供這個(gè)任務(wù)執(zhí)行卿拴,任務(wù)執(zhí)行完畢后這條線程再回到線程池衫仑。這樣隊(duì)列中的任務(wù)反復(fù)調(diào)度,因?yàn)槭峭降亩榛ǎ援?dāng)我們用currentThread打印的時(shí)候文狱,就是同一條線程。
3.如果隊(duì)列中存放的是異步的任務(wù)缘挽,(注意異步可以開(kāi)線程)瞄崇,當(dāng)任務(wù)出隊(duì)后,底層線程池會(huì)提供一個(gè)線程供任務(wù)執(zhí)行壕曼,因?yàn)槭钱惒綀?zhí)行苏研,隊(duì)列中的任務(wù)不需等待當(dāng)前任務(wù)執(zhí)行完畢就可以調(diào)度下一個(gè)任務(wù),這時(shí)底層線程池中會(huì)再次提供一個(gè)線程供第二個(gè)任務(wù)執(zhí)行腮郊,執(zhí)行完畢后再回到底層線程池中楣富。
4.就對(duì)線程完成一個(gè)復(fù)用,而不需要每一個(gè)任務(wù)執(zhí)行都開(kāi)啟新的線程伴榔,也就從而節(jié)約的系統(tǒng)的開(kāi)銷,提高了效率庄萎。在iOS7.0的時(shí)候踪少,使用GCD系統(tǒng)通常只能開(kāi)58條線程,iOS8.0以后糠涛,系統(tǒng)可以開(kāi)啟很多條線程援奢,但是實(shí)在開(kāi)發(fā)應(yīng)用中,建議開(kāi)啟線程條數(shù):35條最為合理忍捡。
二集漾,GCD Global隊(duì)列創(chuàng)建線程進(jìn)行耗時(shí)操作的風(fēng)險(xiǎn)
先思考下如下幾個(gè)問(wèn)題:
- 新建線程的方式有哪些?各自的優(yōu)缺點(diǎn)是什么砸脊?
- dispatch_async 函數(shù)分發(fā)到全局隊(duì)列一定會(huì)新建線程執(zhí)行任務(wù)么具篇?
- 如果全局隊(duì)列對(duì)應(yīng)的線程池如果滿了,后續(xù)的派發(fā)的任務(wù)會(huì)怎么處置凌埂?有什么風(fēng)險(xiǎn)驱显?
答案大致是這樣的:dispatch_async 函數(shù)分發(fā)到全局隊(duì)列不一定會(huì)新建線程執(zhí)行任務(wù),全局隊(duì)列底層有一個(gè)的線程池,如果線程池滿了埃疫,那么后續(xù)的任務(wù)會(huì)被 block 住伏恐,等待前面的任務(wù)執(zhí)行完成,才會(huì)繼續(xù)執(zhí)行栓霜。如果線程池中的線程長(zhǎng)時(shí)間不結(jié)束翠桦,后續(xù)堆積的任務(wù)會(huì)越來(lái)越多,此時(shí)就會(huì)存在 APP crash的風(fēng)險(xiǎn)胳蛮。
比如:
- (void)dispatchTest1 {
for (NSInteger i = 0; i< 10000 ; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self dispatchTask:i];
});
}
}
- (void)dispatchTask:(NSInteger)index {
//模擬耗時(shí)操作销凑,比如DB,網(wǎng)絡(luò),文件讀寫(xiě)等等
sleep(30);
NSLog(@"----:%ld",index);
}
以上邏輯用真機(jī)測(cè)試會(huì)有卡死的幾率鹰霍,并非每次都會(huì)發(fā)生闻鉴,但多嘗試幾次就會(huì)復(fù)現(xiàn),伴隨前后臺(tái)切換茂洒,crash幾率增大孟岛。
下面做一下分析:
參看 GCD 源碼我們可以看到全局隊(duì)列的相關(guān)源碼如下:
DISPATCH_NOINLINE
static void
_dispatch_queue_wakeup_global_slow(dispatch_queue_t dq, unsigned int n)
{
dispatch_root_queue_context_t qc = dq->do_ctxt;
uint32_t i = n;
int r;
_dispatch_debug_root_queue(dq, __func__);
dispatch_once_f(&_dispatch_root_queues_pred, NULL,
_dispatch_root_queues_init);
#if HAVE_PTHREAD_WORKQUEUES
#if DISPATCH_USE_PTHREAD_POOL
if (qc->dgq_kworkqueue != (void*)(~0ul))
#endif
{
_dispatch_root_queue_debug("requesting new worker thread for global "
"queue: %p", dq);
#if DISPATCH_USE_LEGACY_WORKQUEUE_FALLBACK
if (qc->dgq_kworkqueue) {
pthread_workitem_handle_t wh;
unsigned int gen_cnt;
do {
r = pthread_workqueue_additem_np(qc->dgq_kworkqueue,
_dispatch_worker_thread4, dq, &wh, &gen_cnt);
(void)dispatch_assume_zero(r);
} while (--i);
return;
}
#endif // DISPATCH_USE_LEGACY_WORKQUEUE_FALLBACK
#if HAVE_PTHREAD_WORKQUEUE_SETDISPATCH_NP
if (!dq->dq_priority) {
r = pthread_workqueue_addthreads_np(qc->dgq_wq_priority,
qc->dgq_wq_options, (int)i);
(void)dispatch_assume_zero(r);
return;
}
#endif
#if HAVE_PTHREAD_WORKQUEUE_QOS
r = _pthread_workqueue_addthreads((int)i, dq->dq_priority);
(void)dispatch_assume_zero(r);
#endif
return;
}
#endif // HAVE_PTHREAD_WORKQUEUES
#if DISPATCH_USE_PTHREAD_POOL
dispatch_pthread_root_queue_context_t pqc = qc->dgq_ctxt;
if (fastpath(pqc->dpq_thread_mediator.do_vtable)) {
while (dispatch_semaphore_signal(&pqc->dpq_thread_mediator)) {
if (!--i) {
return;
}
}
}
uint32_t j, t_count;
// seq_cst with atomic store to tail <rdar://problem/16932833>
t_count = dispatch_atomic_load2o(qc, dgq_thread_pool_size, seq_cst);
do {
if (!t_count) {
_dispatch_root_queue_debug("pthread pool is full for root queue: "
"%p", dq);
return;
}
j = i > t_count ? t_count : i;
} while (!dispatch_atomic_cmpxchgvw2o(qc, dgq_thread_pool_size, t_count,
t_count - j, &t_count, acquire));
pthread_attr_t *attr = &pqc->dpq_thread_attr;
pthread_t tid, *pthr = &tid;
#if DISPATCH_ENABLE_PTHREAD_ROOT_QUEUES
if (slowpath(dq == &_dispatch_mgr_root_queue)) {
pthr = _dispatch_mgr_root_queue_init();
}
#endif
do {
_dispatch_retain(dq);
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--j);
#endif // DISPATCH_USE_PTHREAD_POOL
}
對(duì)于執(zhí)行的任務(wù)來(lái)說(shuō),所執(zhí)行的線程具體是哪個(gè)線程督勺,則是通過(guò) GCD 的線程池(Thread Pool)來(lái)進(jìn)行調(diào)度
上面貼的源碼渠羞,我們關(guān)注如下的部分:
其中有一個(gè)用來(lái)記錄線程池大小的字段 dgq_thread_pool_size
。這個(gè)字段標(biāo)記著GCD
線程池的大小智哀。摘錄上面源碼的一部分:
uint32_t j, t_count;
// seq_cst with atomic store to tail <rdar://problem/16932833>
t_count = dispatch_atomic_load2o(qc, dgq_thread_pool_size, seq_cst);
do {
if (!t_count) {
_dispatch_root_queue_debug("pthread pool is full for root queue: "
"%p", dq);
return;
}
j = i > t_count ? t_count : i;
} while (!dispatch_atomic_cmpxchgvw2o(qc, dgq_thread_pool_size, t_count,
t_count - j, &t_count, acquire));
也就是說(shuō):
全局隊(duì)列的底層是一個(gè)線程池次询,向全局隊(duì)列中提交的 block,都會(huì)被放到這個(gè)線程池中執(zhí)行瓷叫,如果線程池已滿屯吊,后續(xù)再提交 block 就不會(huì)再重新創(chuàng)建線程。這就是為什么 Demo 會(huì)造成卡頓甚至凍屏的原因摹菠。
三盒卸,避免使用 GCD Global 隊(duì)列創(chuàng)建 Runloop 常駐線程
在做網(wǎng)路請(qǐng)求時(shí)我們常常創(chuàng)建一個(gè) Runloop 常駐線程用來(lái)接收、響應(yīng)后續(xù)的服務(wù)端回執(zhí)次氨,比如NSURLConnection蔽介、AFNetworking等等,我們可以稱這種線程為 Runloop 常駐線程煮寡。
正如上文所述虹蓄,用 GCD Global 隊(duì)列創(chuàng)建線程進(jìn)行耗時(shí)操作是存在風(fēng)險(xiǎn)的。那么我們可以試想下幸撕,如果這個(gè)耗時(shí)操作變成了 runloop 常駐線程薇组,會(huì)是什么結(jié)果?下面做一下分析:
先介紹下 Runloop 常駐線程的原理杈帐,在開(kāi)發(fā)中一般有兩種用法:
- 單一 Runloop 常駐線程:在 APP 的生命周期中開(kāi)啟了唯一的常駐線程來(lái)進(jìn)行網(wǎng)絡(luò)請(qǐng)求体箕,常用于網(wǎng)絡(luò)庫(kù)专钉,或者有維持長(zhǎng)連接需求的庫(kù),比如: AFNetworking 累铅、 SocketRocket跃须。
- 多個(gè) Runloop 常駐線程:每進(jìn)行一次網(wǎng)絡(luò)請(qǐng)求就開(kāi)啟一條 Runloop 常駐線程,這條線程的生命周期的起點(diǎn)是網(wǎng)絡(luò)請(qǐng)求開(kāi)始娃兽,終點(diǎn)是網(wǎng)絡(luò)請(qǐng)求結(jié)束菇民,或者網(wǎng)絡(luò)請(qǐng)求超時(shí)。
單一 Runloop 常駐線程
先說(shuō)第一種用法:
以 AFNetworking 為例投储,AFURLConnectionOperation 這個(gè)類是基于 NSURLConnection 構(gòu)建的第练,其希望能在后臺(tái)線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨(dú)創(chuàng)建了一個(gè)線程玛荞,并在這個(gè)線程中啟動(dòng)了一個(gè) RunLoop:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
多個(gè) Runloop 常駐線程
第二種用法娇掏,我寫(xiě)了一個(gè)小 Demo 來(lái)模擬這種場(chǎng)景,
我們模擬了一個(gè)場(chǎng)景:假設(shè)所有的網(wǎng)絡(luò)請(qǐng)求全部超時(shí)勋眯,或者服務(wù)端根本不響應(yīng)婴梧,然后網(wǎng)絡(luò)庫(kù)超時(shí)檢測(cè)機(jī)制的做法:
#import "Foo.h"
@interface Foo() {
NSRunLoop *_runloop;
NSTimer *_timeoutTimer;
NSTimeInterval _timeoutInterval;
dispatch_semaphore_t _sem;
}
@end
@implementation Foo
- (instancetype)init {
if (!(self = [super init])) {
return nil;
}
_timeoutInterval = 1 ;
_sem = dispatch_semaphore_create(0);
// Do any additional setup after loading the view, typically from a nib.
return self;
}
- (id)test {
// 第一種方式:
// NSThread *networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint0:) object:nil];
// [networkRequestThread start];
//第二種方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[self networkRequestThreadEntryPoint0:nil];
});
dispatch_semaphore_wait(_sem, DISPATCH_TIME_FOREVER);
return @(YES);
}
- (void)networkRequestThreadEntryPoint0:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"CYLTest"];
_runloop = [NSRunLoop currentRunLoop];
[_runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(stopLoop) userInfo:nil repeats:NO];
[_runloop addTimer:_timeoutTimer forMode:NSRunLoopCommonModes];
[_runloop run];//在實(shí)際開(kāi)發(fā)中最好使用這種方式來(lái)確保能runloop退出,做雙重的保障[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:(timeoutInterval+5)]];
}
}
- (void)stopLoop {
CFRunLoopStop([_runloop getCFRunLoop]);
dispatch_semaphore_signal(_sem);
}
@end
如果
for (int i = 0; i < 300 ; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[[Foo new] test];
NSLog(@"??類名與方法名:%@(在第%@行)客蹋,描述:%@", @(__PRETTY_FUNCTION__), @(__LINE__), @"");
});
}
以上邏輯用真機(jī)測(cè)試會(huì)有卡死的幾率塞蹭,并非每次都會(huì)發(fā)生,但多嘗試幾次就會(huì)復(fù)現(xiàn)讶坯,伴隨前后臺(tái)切換番电,crash幾率增大。
其中我們采用了 GCD 全局隊(duì)列的方式來(lái)創(chuàng)建常駐線程辆琅,因?yàn)樵趧?chuàng)建時(shí)可能已經(jīng)出現(xiàn)了全局隊(duì)列的線程池滿了的情況漱办,所以 GCD 派發(fā)的任務(wù),無(wú)法執(zhí)行婉烟,而且我們把超時(shí)檢測(cè)的邏輯放進(jìn)了這個(gè)任務(wù)中洼冻,所以導(dǎo)致的情況就是,有很多任務(wù)的超時(shí)檢測(cè)功能失效了隅很。此時(shí)就只能依賴于服務(wù)端響應(yīng)來(lái)結(jié)束該任務(wù)(服務(wù)端響應(yīng)能結(jié)束該任務(wù)的邏輯在 Demo 中未給出),但是如果再加之服務(wù)端不響應(yīng)率碾,那么任務(wù)就永遠(yuǎn)不會(huì)結(jié)束叔营。后續(xù)的網(wǎng)絡(luò)請(qǐng)求也會(huì)就此 block 住,造成 crash所宰。
如果我們把 GCD 全局隊(duì)列換成 NSThread 的方式绒尊,那么就可以保證每次都會(huì)創(chuàng)建新的線程。
注意:文章中只演示的是超時(shí) cancel runloop 的操作仔粥,實(shí)際項(xiàng)目中一定有其他主動(dòng) cancel runloop 的操作婴谱,就比如網(wǎng)絡(luò)請(qǐng)求成功或失敗后需要進(jìn)行cancel操作蟹但。代碼中沒(méi)有展示網(wǎng)絡(luò)請(qǐng)求成功或失敗后的 cancel 操作。
Demo 的這種模擬可能比較極端谭羔,但是如果你維護(hù)的是一個(gè)像 AFNetworking 這樣的一個(gè)網(wǎng)絡(luò)庫(kù)华糖,你會(huì)放心把創(chuàng)建常駐線程這樣的操作交給 GCD 全局隊(duì)列嗎?因?yàn)檎麄€(gè) APP 是在共享一個(gè)全局隊(duì)列的線程池瘟裸,那么如果 APP 把線程池沾滿了客叉,甚至線程池長(zhǎng)時(shí)間占滿且不結(jié)束,那么 AFNetworking 就自然不能再執(zhí)行任務(wù)了话告,所以我們看到兼搏,即使是只會(huì)創(chuàng)建一條常駐線程, AFNetworking 依然采用了 NSThread 的方式而非 GCD 全局隊(duì)列這種方式沙郭。
注釋:以下方法存在于老版本AFN 2.x 中佛呻。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
正如你所看到的,沒(méi)有任何一個(gè)庫(kù)會(huì)用 GCD 全局隊(duì)列來(lái)創(chuàng)建常駐線程病线,而你也應(yīng)該
避免使用 GCD Global 隊(duì)列來(lái)創(chuàng)建 Runloop 常駐線程吓著。
隊(duì)列(queue): 用來(lái)存放任務(wù)
隊(duì)列不是線程 隊(duì)列中存放的任務(wù)最后都要由線程來(lái)執(zhí)行
隊(duì)列的原則:(FIFO) First In First Out 先進(jìn)先出 后進(jìn)后出
隊(duì)列類型
串行隊(duì)列:Serial Dispatch Queue
存放順序執(zhí)行的任務(wù)
線程池只提供一個(gè)線程用來(lái)執(zhí)行任務(wù)
一個(gè)任務(wù)執(zhí)行完畢 在執(zhí)行下一個(gè)任務(wù)
并發(fā)隊(duì)列:Concurrent Dispatch Queue
存放想要同時(shí)并發(fā)執(zhí)行的任務(wù) 可以開(kāi)啟多線程 具體數(shù)量由底層GCD負(fù)責(zé)
線程池可以提供多個(gè)線程來(lái)執(zhí)行任務(wù) 具體數(shù)量由底層GCD負(fù)責(zé)
并發(fā)執(zhí)行 性能高 執(zhí)行順序不固定 費(fèi)電因?yàn)榻^大多數(shù)會(huì)使用全局隊(duì)列,全局隊(duì)列本身就是并發(fā)隊(duì)列
dispatch_queue_create
//串行隊(duì)列
dispatch_queue_t SerialQueue;
//并發(fā)隊(duì)列
dispatch_queue_t ConcurrentQueue;
//后面這個(gè)參數(shù)可以不寫(xiě)的 默認(rèn)填NULL就是串行
SerialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
ConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
系統(tǒng)隊(duì)列
系統(tǒng)提供了兩個(gè)隊(duì)列
** 主隊(duì)列**:dispatch_get_main_queue
屬于串行隊(duì)列
負(fù)責(zé)調(diào)度主線程度的任務(wù)氧苍,沒(méi)有辦法開(kāi)辟新的線程夜矗。
所以主隊(duì)列下的任務(wù)不管是異步任務(wù)還是同步任務(wù)都不會(huì)開(kāi)辟線程
任務(wù)只會(huì)在主線程順序執(zhí)行。
主隊(duì)列異步任務(wù):現(xiàn)將任務(wù)放在主隊(duì)列中让虐,但是不是馬上執(zhí)行紊撕,等到主隊(duì)列中的其它所有除我們使用代碼添加到主隊(duì)列的任務(wù)的任務(wù)都執(zhí)行完畢之后才會(huì)執(zhí)行我們使用代碼添加的任務(wù)。
主隊(duì)列同步任務(wù):容易阻塞主線程赡突,所以不要這樣寫(xiě)对扶。原因:我們自己代碼任務(wù)需要馬上執(zhí)行,但是主線程正在執(zhí)行代碼任務(wù)的方法體惭缰,因此代碼任務(wù)就必須等待浪南,而主線程又在等待代碼任務(wù)的完成好去完成下面的任務(wù),因此就形成了相互等待漱受。整個(gè)主線程就被阻塞了络凿。
全局隊(duì)列: dispatch_get_global_queue
屬于并發(fā)隊(duì)列
一般情況下 并發(fā)任務(wù)都可以放在全局并發(fā)隊(duì)列中
全局隊(duì)列和并發(fā)隊(duì)列的區(qū)別:
1 全局隊(duì)列沒(méi)有名字,但是并發(fā)隊(duì)列有名字昂羡。有名字可以便于查看系統(tǒng)日志
2 全局隊(duì)列是所有應(yīng)用程序共享的絮记。
3 在mrc的時(shí)候,全局隊(duì)列不用手動(dòng)釋放虐先,但是并發(fā)隊(duì)列需要怨愤。
dispatch_queue_t
dispatch_queue_t mymainQueue;
dispatch_queue_t myglobalQueue;
mymainQueue = dispatch_get_main_queue();
myglobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**參數(shù)說(shuō)明:
參數(shù)1:代表該任務(wù)的優(yōu)先級(jí),默認(rèn)寫(xiě)0就行蛹批,不要使用系統(tǒng)提供的枚舉類型撰洗,因?yàn)閕os7和ios8的枚舉數(shù)值不一樣篮愉,使用數(shù)字可以通用。
DISPATCH_QUEUE_PRIORITY_HIGH 2
DISPATCH_QUEUE_PRIORITY_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW (-2)
DISPATCH_QUEUE_PRIORITY_BACKGROUND (-32768)
參數(shù)2:蘋(píng)果保留關(guān)鍵字差导,一般寫(xiě)0或NULL
所以也可以寫(xiě)為myglobalQueue = dispatch_get_global_queue(0, 0);
*/
同步異步
sync
同步運(yùn)行:
如果是同步執(zhí)行 隊(duì)列會(huì)等任務(wù)結(jié)束后 再調(diào)度后續(xù)的任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
;
async
異步運(yùn)行:
dispatch_sync(dispatch_queue_t queue, ^(void)block ) *dispatch_block_t
就是無(wú)返回值 無(wú)參數(shù)的block
常用用法介紹
1.經(jīng)典用法(子線程下載(耗時(shí)操作),主線程刷新UI)
dispatch_async(dispatch_get_global_queue(0,0), ^{
//執(zhí)行耗時(shí)的異步操作…
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程试躏,執(zhí)行UI刷新操作
});
});
2.GCD的延時(shí)執(zhí)行
延時(shí)是延時(shí)任務(wù)加入到隊(duì)列的時(shí)間 不是延時(shí)任務(wù)執(zhí)行的時(shí)間
//1.延時(shí)不是一定時(shí)間后執(zhí)行相應(yīng)的任務(wù),而是一定時(shí)間后柿汛,將任務(wù)加入到隊(duì)列中(隊(duì)列里面再分配執(zhí)行的時(shí)間)
//2.主線程 RunLoop 1/60秒檢測(cè)時(shí)間冗酿,追加的時(shí)間范圍 3s~(3+1/60)s
//3.在哪個(gè)線程執(zhí)行,跟隊(duì)列類型有關(guān)
//dispatch_after(一定時(shí)間后络断,將執(zhí)行的操作加入到隊(duì)列中)
//dispatch_time_t when 指定時(shí)間
/* NSEC_PER_SEC 秒
* NSEC_PER_MSEC 毫秒
* NSEC_PER_USEC 微秒
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_queue_t que = dispatch_queue_create("h", DISPATCH_QUEUE_SERIAL);
//1.第一種用法
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"第一種延時(shí) code to be executed on the main queue after delay");
});
//2.第二種用法
//dispatch_function_t work 執(zhí)行的c語(yǔ)言方法
dispatch_after_f(time, que, NULL, fun1);
//3.第三種用法
dispatch_after(time, que, ^{
NSLog(@"第三種延時(shí) code to be executed on the main queue after delay");
});
------代表方法外的分割-----
void fun1(){
NSLog(@"第二種延時(shí) code to be executed on the main queue after delay");
}
3.異步執(zhí)行:
//dispatch_async +全局并發(fā)隊(duì)列(可以開(kāi)啟多條線程)
//dispatch_async +自己創(chuàng)建的串行隊(duì)列(開(kāi)啟一條線程)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
dispatch_async(dispatch_get_main_queue(), ^{
// 主隊(duì)列異步
});
一次性執(zhí)行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
4.一次性執(zhí)行和多次執(zhí)行
/*
一次執(zhí)行:dispatch_once
作用:在多線程的情況下裁替,同樣能夠保證指定的代碼塊只被執(zhí)行一次
快捷鍵:
應(yīng)用場(chǎng)景:?jiǎn)卫O(shè)計(jì)模式
*/
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"塊代碼只能執(zhí)行一次");
});
/*
多次執(zhí)行:dispatch_apply
以指定的次數(shù)將指定的Block加入到指定的隊(duì)列中 并等待隊(duì)列中操作全部完成.
當(dāng)指定隊(duì)列為串行時(shí) 有序單線程執(zhí)行
當(dāng)指定隊(duì)列為并發(fā)隊(duì)列時(shí) 多線程無(wú)序執(zhí)行
*/
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_CONCURRENT);/
dispatch_apply(3, q1, ^(size_t index) {
NSLog(@"重要的事情說(shuō)三遍 第%zu遍 %@",index,[NSThread currentThread]);
});
5.dispatch_group分組
/**
作用:所有任務(wù)執(zhí)行完成之后,統(tǒng)一通知用戶
可以實(shí)現(xiàn)監(jiān)聽(tīng)一組任務(wù)是否完成 完成后得到通知執(zhí)行其他的操作
不包括延時(shí)任務(wù) 因?yàn)檠訒r(shí)是任務(wù)放進(jìn)隊(duì)列的時(shí)間
*/
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t q1 = dispatch_queue_create("a1", DISPATCH_QUEUE_SERIAL);
dispatch_block_t t1 = ^{
NSLog(@"任務(wù)1");
};
dispatch_async(q1, t1);
dispatch_group_async(group, q1, ^{
NSLog(@"group1");
});
dispatch_group_async(group, q1, ^{
NSLog(@"group2");
});
dispatch_group_notify(group, q1, ^{
NSLog(@"end");
});
6.dispatch_barrier_async的使用
/*
dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行貌笨,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行
*/
dispatch_queue_t queue = dispatch_queue_create("aa", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async1");
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_barrier_async2");
});
dispatch_async(queue, ^{
NSLog(@"dispatch_async3");
});
7弱判、dispatch_set_target_queue
使用dispatch_set_target_queue將多個(gè)串行的queue指定到了同一目標(biāo),那么著多個(gè)串行queue在目標(biāo)queue上就是同步執(zhí)行的锥惋,不再是并行執(zhí)行昌腰。
dispatch_queue_t tq = dispatch_queue_create("tq",DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q1 = dispatch_queue_create("q1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q2 = dispatch_queue_create("q2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t q3 = dispatch_queue_create("q3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(q1, tq);
dispatch_set_target_queue(q2, tq);
dispatch_set_target_queue(q3, tq);
dispatch_async(q1, ^{
NSLog(@"1 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3.f];
NSLog(@"1 out%@",[NSThread currentThread]);
});
dispatch_async(q2, ^{
NSLog(@"2 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.f];
// dispatch_suspend(queue1);
NSLog(@"2 out %@",[NSThread currentThread]);
});
dispatch_async(q3, ^{
NSLog(@"3 in %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:1.f];
NSLog(@"3 out %@",[NSThread currentThread]);
});
GCD 死鎖
-(void)viewDidLoad { [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); NSLog(@"3"); }
我們來(lái)一步一步解析。首先dispatch_sync
是同步的膀跌,它會(huì)造成一個(gè)后果那就是阻塞主線程遭商,并且會(huì)一直會(huì)等待block
,而block
放入到了主線程隊(duì)列dispatch_get_main_queue()
中捅伤,這是一個(gè)FIFA
隊(duì)列劫流,就是先進(jìn)先出的隊(duì)列。他在等待主線程執(zhí)行丛忆。那么一目了然了祠汇。我用偽代碼敲出來(lái)大家就知道了
MainThread { dispatch_get_main_queue(){ syncblock(); } }
MainThread
等待dispatch_sync,dispatch_sync
等待block
,block
等待mainquen
,maiden
等待MainThread
,而MainThread
等待dispatch_sync
熄诡。這樣就形成了一個(gè)死循環(huán)可很。俗稱DeadLock
死鎖。
NSLog(@"1"); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"2"); } }); dispatch_async(dispatch_get_main_queue(), ^{ while (1) { NSLog(@"3"); } }); NSLog(@"4");
結(jié)果大家一運(yùn)行就知道凰浮,
為什么第一個(gè)只輸出1
和4
和2
我抠。我們來(lái)解析問(wèn)題的代碼
這里面有兩部異步block
但是放到了主線程隊(duì)列里面,但是block
里面執(zhí)行的是一個(gè) while (1)
的死循環(huán)
我們一步一步解析袜茧。 主線程隊(duì)列是個(gè)FIFO
也就是先進(jìn)先出屿良,先進(jìn)的完了才執(zhí)行第二個(gè)。而當(dāng)
^{ while (1) { NSLog(@"2"); } }
放入到主線程隊(duì)列后惫周,它就永遠(yuǎn)執(zhí)行不完,永遠(yuǎn)不會(huì)退出康栈,所以
^{ while (1) { NSLog(@"3"); }
這個(gè)只能永遠(yuǎn)的等待递递,而這兩個(gè)block又是異步的不會(huì)阻塞主線程所以主線程的輸出依然木有問(wèn)題喷橙。