427,GCD執(zhí)行原理(面試點(diǎn):GCD有一個(gè)底層線程池串述,注意:開(kāi)多少條線程是否底層線程池決定的执解,池是系統(tǒng)自動(dòng)來(lái)維護(hù)的,不需要我們程序員來(lái)維護(hù)的剖煌,dispatch_async 函數(shù)分發(fā)到全局隊(duì)列一...

一材鹦,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)度

image.png

上面貼的源碼渠羞,我們關(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等待blockblock等待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è)只輸出142我抠。我們來(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)題喷橙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市登舞,隨后出現(xiàn)的幾起案子贰逾,更是在濱河造成了極大的恐慌,老刑警劉巖菠秒,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疙剑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡践叠,警方通過(guò)查閱死者的電腦和手機(jī)言缤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)禁灼,“玉大人管挟,你說(shuō)我怎么就攤上這事∨叮” “怎么了僻孝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)守谓。 經(jīng)常有香客問(wèn)我穿铆,道長(zhǎng),這世上最難降的妖魔是什么斋荞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任荞雏,我火速辦了婚禮,結(jié)果婚禮上譬猫,老公的妹妹穿的比我還像新娘讯檐。我一直安慰自己,他們只是感情好染服,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布别洪。 她就那樣靜靜地躺著,像睡著了一般柳刮。 火紅的嫁衣襯著肌膚如雪挖垛。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天秉颗,我揣著相機(jī)與錄音痢毒,去河邊找鬼。 笑死蚕甥,一個(gè)胖子當(dāng)著我的面吹牛哪替,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菇怀,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼凭舶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晌块!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起帅霜,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匆背,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后身冀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钝尸,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年搂根,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了珍促。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兄墅,死狀恐怖踢星,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隙咸,我是刑警寧澤沐悦,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站五督,受9級(jí)特大地震影響藏否,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜充包,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一副签、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧基矮,春花似錦淆储、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至钢悲,卻和暖如春点额,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背莺琳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工还棱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惭等。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓珍手,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子琳要,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容