GCD底層

前言

書接上回函數(shù)與隊列酝惧,我們根據(jù)GCD底層源碼分析芭碍,知道了隊列分為串行并發(fā)兩種類型颓屑,有兩種常用的隊列:主隊列全局隊列官脓,其中主隊列類型是串行仿贬,而全局隊列類型是并發(fā)茁瘦。
隊列的底層創(chuàng)建流程大致分析完了品抽,今天我們再重點看看函數(shù)的底層執(zhí)行流程,就是GCD的Block的初始化甜熔,以及被調(diào)用的過程圆恤。我們以異步并發(fā)為例看看??

    dispatch_queue_t conque = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(conque, ^{
        NSLog(@"12334");
    });

dispatch_async底層

#ifdef __BLOCKS__
void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;

    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
#endif
  1. 入?yún)ork就是block,需要執(zhí)行的任務(wù);
  2. 局部變量dc dc_flag都是為了初始化qos -->_dispatch_continuation_init(dc, dq, work, 0, dc_flags);
  3. 初始化完成后盆昙,接著_dispatch_continuation_async

dispatch_async的源碼不多羽历,就這么幾行,但是我們知道淡喜,異步并發(fā)任務(wù)是會開啟子線程的秕磷,在子線程去執(zhí)行block任務(wù),所以炼团,我們需要關(guān)注2個點:

  • 子線程創(chuàng)建的時機點
  • 任務(wù)block執(zhí)行的時機點

1 子線程的創(chuàng)建流程

我們先進(jìn)入_dispatch_continuation_init的流程澎嚣,看看是否有子線程創(chuàng)建?

1.1 _dispatch_continuation_init 底層流程

接著來到_dispatch_continuation_init_f瘟芝,其中release的func是第4個入?yún)⒁滋遥鴚ork就是需要執(zhí)行的任務(wù)是ctxt第3個入?yún)ⅲ^續(xù)看看_dispatch_continuation_init_f

  • _dispatch_continuation_init_f
  • _dispatch_continuation_voucher_set
  • _dispatch_continuation_priority_set --> dc的dc_priority的設(shè)置 ??

以上并沒有發(fā)現(xiàn)關(guān)于子線程創(chuàng)建的代碼锌俱,接下來我們?nèi)?code>_dispatch_continuation_async找找晤郑。

1.2 _dispatch_continuation_async底層流程

根據(jù)返回值,鎖定到了dx_push贸宏。全局搜索dx_push??

繼續(xù)看dq_push??


如果是并發(fā)隊列造寝,那么.dq_push = _dispatch_lane_concurrent_push,接著來到
_dispatch_lane_concurrent_push??

顯然是非柵欄函數(shù),那么進(jìn)入_dispatch_continuation_redirect_push??

do_targetq是什么呢吭练?得回到隊列的創(chuàng)建dispatch_queue_create去查看??

那么匹舞,_dispatch_continuation_redirect_push里的dx_push時的隊列是_dispatch_get_root_queue()??

同理,找dispatch_queue_global_t 對應(yīng)的 dq_push 的方法

至此线脚,我們知道了dispatch_async子線程創(chuàng)建的調(diào)用鏈??(層級比較深)

  1. dispatch_async --> _dispatch_continuation_async --> dx_push --> dq_push --> 并發(fā)隊列:_dispatch_lane_concurrent_push --> _dispatch_continuation_redirect_push
  2. _dispatch_continuation_redirect_push --> dx_push(此時是global_queue) -->_dispatch_root_queue_push --> _dispatch_root_queue_push_inline-->_dispatch_root_queue_poke-->_dispatch_root_queue_poke_slow -->線程池調(diào)度,創(chuàng)建線程pthread_create

2 任務(wù)Block的調(diào)用流程

那么接下來的問題就是 block何時調(diào)用?

再看哪里調(diào)用的block --> 類似這樣的代碼block(xxx)? 我們可以在dispatch_async的任務(wù)block中打斷點叫榕,然后bt查看調(diào)用棧??

注意到在調(diào)用棧中有一個_dispatch_worker_thread2浑侥,現(xiàn)在重點就來到 --> 什么時候調(diào)起的_dispatch_worker_thread2

我們先全局搜索一下_dispatch_worker_thread2??

發(fā)現(xiàn)全在一個方法里面 --> _dispatch_root_queues_init_once中晰绎,同理寓落,全局搜索??

再全局搜索_dispatch_root_queues_init ??

_dispatch_root_queue_poke_slow是否很熟悉? 就是我們上面在查找創(chuàng)建子線程時調(diào)用棧走過的方法荞下,那么此時任務(wù)block的調(diào)用和子線程的創(chuàng)建產(chǎn)生了聯(lián)系伶选,這個聯(lián)系就是_dispatch_root_queue_poke_slow

2.1 _dispatch_root_queues_init

現(xiàn)在我們重點來看看_dispatch_root_queues_init尖昏,是否真的調(diào)用任務(wù)block仰税?

DISPATCH_STATIC_GLOBAL(dispatch_once_t _dispatch_root_queues_pred);
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_root_queues_init(void)
{
    dispatch_once_f(&_dispatch_root_queues_pred, NULL,
            _dispatch_root_queues_init_once);
}

dispatch_once_f是否有些熟悉?莫非是單例?我們平時寫的單例是這樣的??

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // input your code
    });

搜索一下dispatch_once源碼??

果然抽诉, dispatch_once_f是個單例方法陨簇。

2.2 單例的底層

dispatch_once_f源碼??

DISPATCH_NOINLINE
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
    if (likely(v == DLOCK_ONCE_DONE)) {
        return;
    }
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
    if (likely(DISPATCH_ONCE_IS_GEN(v))) {
        return _dispatch_once_mark_done_if_quiesced(l, v);
    }
#endif
#endif
    if (_dispatch_once_gate_tryenter(l)) {
        return _dispatch_once_callout(l, ctxt, func);
    }
    return _dispatch_once_wait(l);
}

總之,這個_dispatch_once_gate_tryenter判斷條件迹淌,就能保證當(dāng)前只有一個線程進(jìn)去執(zhí)行代碼河绽,那為什么只能執(zhí)行一次呢己单?還是看_dispatch_once_gate_broadcast 里的 _dispatch_once_mark_done

2.3 回到Block的調(diào)用時機

即什么時候調(diào)起的_dispatch_worker_thread2
_dispatch_root_queues_init時耙饰,單例執(zhí)行的任務(wù)block是_dispatch_root_queues_init_once??


再來看看_dispatch_root_queues_init_once??

上圖可知纹笼,在_dispatch_root_queues_init_once中完成了線程與任務(wù)_dispatch_worker_thread2的綁定過程。接下來就看看_dispatch_worker_thread2的大致流程苟跪。

2.4 _dispatch_worker_thread2底層

最終廷痘,我們來到了dx_invoke_dispatch_continuation_invoke_inline

  • _dispatch_continuation_invoke_inline


  • dx_invoke
    dx_push道理一樣削咆,是個宏定義??
#define dx_invoke(x, y, z) dx_vtable(x)->do_invoke(x, y, z)

都是從dx_vtable中的屬性而來牍疏。再搜索do_invoke??

因為是并發(fā)隊列queue_concurrent,對應(yīng)的invoke方法名是_dispatch_lane_invoke??

invoke是入?yún)?code>_dispatch_queue_class_invoke_handler_t類型拨齐,也就是方法_dispatch_lane_invoke2??

又回到了_dispatch_continuation_pop_inline??

此時肯定不會再次觸發(fā)dx_invoke鳞陨,不然就遞歸了,所以會走到_dispatch_continuation_invoke_inline瞻惋,剩下的流程就是調(diào)用block()了厦滤。

至此,我們跟著底層源碼弄清楚了block()的調(diào)用流程??

  1. 通過在block任務(wù)中打斷點歼狼,LLDB bt指令查看調(diào)用棧信息掏导,找到_dispatch_worker_thread2
  2. 搜索調(diào)用_dispatch_worker_thread2的地方羽峰,找到_dispatch_root_queues_init --> _dispatch_root_queues_init_once趟咆;
  3. 接著我們在_dispatch_root_queues_init_once中發(fā)現(xiàn)了子線程的創(chuàng)建,并綁定了block任務(wù)_dispatch_worker_thread2梅屉;
  4. 接著我們繼續(xù)查看_dispatch_worker_thread2的底層源碼值纱,發(fā)現(xiàn)了調(diào)用block任務(wù)的時機點。

總結(jié)

我們通過異步并發(fā)的案例坯汤,圍繞兩個點:子線程的創(chuàng)建時機任務(wù)Block的調(diào)用時機虐唠,分析了dispatch_async的底層大致流程,同時也關(guān)聯(lián)到單例的底層實現(xiàn)原理惰聂。

補充1:柵欄函數(shù)

dispatch_barrier_(a)sync被稱作柵欄函數(shù)疆偿,不論是同步柵欄還是異步柵欄,都必須等上面的任務(wù)執(zhí)行完搓幌,柵欄函數(shù)本身的block任務(wù)才會執(zhí)行杆故,而同步與異步的差別在于??

  1. 同步柵欄必須自己的block任務(wù)執(zhí)行完成,下面的任務(wù)block才會執(zhí)行鼻种,這就表示同步阻塞的是當(dāng)前的線程反番。
  2. 異步卻不需要等自己的任務(wù)block執(zhí)行,下面的代碼會接著執(zhí)行,這就表示異步阻塞的是隊列(queue)罢缸。

柵欄函數(shù)一個重要的點:必須是并發(fā)隊列篙贸,并且是自定義的并發(fā)隊列

不可用global_queue全局并發(fā)隊列

示例??

- (void)demo2{
//    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
    // 這里是可以的額!
    /* 1.異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"123");
    });
    /* 2. 柵欄函數(shù) */ // - dispatch_barrier_sync
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
    });
    /* 3. 異步函數(shù) */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"加載那么多,喘口氣!!!");
    });
    NSLog(@"**********起來干!!");
 
}

運行??

柵欄根本沒有作用枫疆,按道理123應(yīng)該優(yōu)先currentThead的爵川。

作用可等同于鎖

示例??

- (void)demo3{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10; i++) {
        dispatch_async(concurrentQueue, ^{
            NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
            NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            
            @synchronized (self) {
                [self.mArray addObject:image];
                NSLog(@"self.mArray添加image:%@", imageName);
            }
        });
    }
}

運行??


因為是異步并發(fā)隊列,所以會有很多子線程處理圖片的添加過程息楔,但是日志表明寝贡,當(dāng)前的圖片是一張張的添加進(jìn)數(shù)組的,這就是鎖的作用值依。
我們再換成柵欄函數(shù)??

dispatch_barrier_async(concurrentQueue , ^{
    [self.mArray addObject:image];
    NSLog(@"self.mArray添加image:%@", imageName);
});

運行??

一樣的效果圃泡。

補充2:同步函數(shù)

dispatch_sync 沿著調(diào)用鏈,發(fā)現(xiàn)愿险,如果是串行隊列颇蜡,那么就會走_dispatch_barrier_sync_f。??

接著看_dispatch_barrier_sync_f的源碼??

再回頭看看_dispatch_queue_try_acquire_barrier_sync里的流程??

補充3:死鎖問題

在主線程中運行下面代碼:

dispatch_sync(mainQueue, ^{
    NSLog(@"123");
});

打斷點辆亏,查看調(diào)用棧信息


會走到_dispatch_sync_f_slow风秤,再看源碼??

接著看_dispatch_lock_is_locked_by

至此,最終會執(zhí)行DISPATCH_CLIENT_CRASH((uintptr_t)dq_state, "dispatch_sync called on queue " "already owned by current thread");扮叨,直接crash報錯缤弦。

這個錯就是,主隊列中添加同步執(zhí)行的block任務(wù)彻磁,這個任務(wù)會交給當(dāng)前的主線程去處理執(zhí)行碍沐,但是因為是同步函數(shù)主線程又要等待block任務(wù)的執(zhí)行完成衷蜓,才能接著往下走抢韭,說白了,就是block的執(zhí)行等待自己block的完成恍箭,矛盾了!這個矛盾的現(xiàn)象就被稱作死鎖瞧省。

所以扯夭,這個死鎖具體跟哪個隊列是沒有關(guān)系的,不管是主隊列鞍匾,還是其它自定義的串行隊列交洗,只要block任務(wù)自己等待自己完成,就會crash死鎖橡淑!

補充4:信號量

信號量dispatch_semaphore_t實際運用的場景比較少构拳,涉及的核心的函數(shù)有:

  1. dispatch_semaphore_create 創(chuàng)建信號量,指定最大并發(fā)數(shù)
  2. dispatch_semaphore_signal 發(fā)送信號
  3. dispatch_semaphore_wait 等待信號

示例??

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);
    
    //任務(wù)1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)1");
        sleep(1);
        NSLog(@"任務(wù)1完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任務(wù)2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)2");
        sleep(1);
        NSLog(@"任務(wù)2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任務(wù)3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"執(zhí)行任務(wù)3");
        sleep(1);
        NSLog(@"任務(wù)3完成");
        dispatch_semaphore_signal(sem);
    });

運行??


信號量的發(fā)送與等待,配合使用置森,可以保證當(dāng)前任務(wù)的按一定順序去執(zhí)行斗埂。

我們現(xiàn)在來看看,dispatch_semaphore_signaldispatch_semaphore_wait底層源碼是如何處理的凫海?以dispatch_semaphore_signal為例??

dispatch_semaphore_signal

接著我們看看os_atomic_inc2o??

#define os_atomic_inc2o(p, f, m) \
        os_atomic_add2o(p, f, 1, m)

os_atomic_add2o??

#define os_atomic_add2o(p, f, v, m) \
        os_atomic_add(&(p)->f, (v), m)

帶入值呛凶,可知??
os_atomic_inc2o(dsema, dsema_value, release) --> os_atomic_add2o(dsema, dsema_value, 1, release) --> os_atomic_add(dsema_value, 1, release)
接著看os_atomic_add

#define os_atomic_add(p, v, m) \
        _os_atomic_c11_op((p), (v), m, add, +)

#define _os_atomic_c11_op(p, v, m, o, op) \
        ({ _os_atomic_basetypeof(p) _v = (v), _r = \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \
        memory_order_##m); (__typeof__(_r))(_r op _v); })

接著:os_atomic_add(dsema_value, 1, release) --> _os_atomic_c11_op(dsema_value, 1, release, add, +) ,然后_os_atomic_c11_op是一個函數(shù)行贪,重點看atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), _v, \ memory_order_##m)這句漾稀,再帶入入?yún)⒅担褪牵?code>atomic_fetch_add_explicit(3個入?yún)?建瘫,atomic_fetch_add_explicit是什么意思呢崭捍???詳細(xì)請參考

綜上,我們知道了dispatch_semaphore_signal就是++自加操作啰脚。

dispatch_semaphore_wait

所以殷蛇,dispatch_semaphore_signal表示信號量+1,dispatch_semaphore_wait表示信號量-1拣播。如果在+1之前晾咪,信號量是-1的話,結(jié)果是0贮配,那么就會進(jìn)入signal_slow信號量永久等待的過程谍倦,同理,在wait之前信號量是0的話泪勒,減1結(jié)果是負(fù)1昼蛀,也會進(jìn)入signal_slow信號量永久等待的過程。

補充5:調(diào)度組

現(xiàn)在有一個場景:我們先要做很多的異步操作圆存,例如網(wǎng)絡(luò)請求叼旋,需要等待所有的異步網(wǎng)絡(luò)請求執(zhí)行完成后,才能執(zhí)行下一步的操作沦辙。我們都知道夫植,網(wǎng)絡(luò)請求的執(zhí)行完成,依賴當(dāng)前網(wǎng)絡(luò)的狀態(tài)油讯、服務(wù)器響應(yīng)的速度以及網(wǎng)絡(luò)帶寬的大小等其它很多因素详民,每個網(wǎng)絡(luò)請求完成的時間是無法把控的,更何況要去找所有網(wǎng)絡(luò)請求執(zhí)行完成的這個時機點呢陌兑?很難吧沈跨!如果此時采用柵欄函數(shù),你準(zhǔn)備欄在哪里兔综?此時饿凛,需要用到GDC另一個常用的API:dispatch_group_t調(diào)度組狞玛。

我們先看看一個使用的示例:下載兩張圖片,其中一張是水印涧窒,兩張圖片下載完成后心肪,再將兩張圖片整合生成一張帶水印的圖片展示??

- (void)groupDemo {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        //創(chuàng)建調(diào)度組
        NSString *logoStr1 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
        NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr1]];
        UIImage *image1 = [UIImage imageWithData:data1];
        [self.mArray addObject:image1];
        dispatch_group_leave(group);
    });


    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        //創(chuàng)建調(diào)度組
       NSString *logoStr2 = @"https://f12.baidu.com/it/u=711217113,818398466&fm=72";
        NSData *data2 = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr2]];
        UIImage *image2 = [UIImage imageWithData:data2];
        [self.mArray addObject:image2];
        dispatch_group_leave(group);
    });
   
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        UIImage *newImage = nil;
       NSLog(@"數(shù)組個數(shù):%ld",self.mArray.count);
       for (int i = 0; i<self.mArray.count; i++) {
           UIImage *waterImage = self.mArray[i];
           newImage =[KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
       }
        self.imageView.image = newImage;
    });

}

添加水印的代碼??

/**
 給指定圖片加圖片水印

 @param waterImage 水印圖片
 @param rect 位置
 @return 返回圖片水印照片
 */

+ (UIImage *)kc_WaterImageWithWaterImage:(UIImage *)waterImage backImage:(UIImage *)backImage waterImageRect:(CGRect)rect{
    
    UIImage *newBackImage = backImage;
    if (!newBackImage) {
        newBackImage = [UIImage imageNamed:@"backImage"];
    }
    return [self kc_WaterImageWithImage:newBackImage waterImage:waterImage waterImageRect:rect];
}

// 給圖片添加圖片水印
+ (UIImage *)kc_WaterImageWithImage:(UIImage *)image waterImage:(UIImage *)waterImage waterImageRect:(CGRect)rect{
    //1.獲取圖片
    //2.開啟上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    //3.繪制背景圖片
    [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    //繪制水印圖片到當(dāng)前上下文
    [waterImage drawInRect:rect];
    //4.從上下文中獲取新圖片
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    //5.關(guān)閉圖形上下文
    UIGraphicsEndImageContext();
    //返回圖片
    return newImage;
}
特殊1

再看一個相對簡單的案例,但是我們不按常理出牌杀狡,將dispatch_group_notify寫到最前面??

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他們回來了蒙畴,我準(zhǔn)備在主線程更新UI");
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模擬耗時
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    NSLog(@"主線程任務(wù)正常運行");
}

run??


事實證明-->把notify寫在前面,發(fā)現(xiàn)會提前調(diào)用notify呜象。why膳凝?

特殊2

接著我們把enter多寫一個??

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模擬耗時
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他們回來了,我準(zhǔn)備在主線程更新UI");
    });
        
    NSLog(@"主線程任務(wù)正常運行");
}

run??

可見恭陡,enter多了時蹬音,主線程被卡死了,但是沒有crash休玩。

特殊3

我們再多寫一個leave著淆,??

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模擬耗時
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_leave(group);
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他們回來了,我準(zhǔn)備在主線程更新UI");
    });
        
    NSLog(@"主線程任務(wù)正常運行");
}

run??

leave多了時拴疤,直接crash永部!

特殊4

我們使用dispatch_group_async,看看是打印什么呐矾?

- (void)groupDemo2{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        sleep(1); // 模擬耗時
        NSLog(@"123");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"456");
        dispatch_group_leave(group);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"7890");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"他們回來了苔埋,我準(zhǔn)備在主線程更新UI");
    });
        
    NSLog(@"主線程任務(wù)正常運行");
}

run??

根據(jù)打印的日志,因為第一個group_async里有做一個sleep(1)蜒犯,所以456先打印逻淌,緊接著主線程的日志打印马澈,再是dispatch_group_async的7890打印小压,然后123打印丁逝,最后notify的日志打印,那么就說明淘菩,在有dispatch_group_async的情況下遵班,notify依舊最后執(zhí)行,與有enterleave的情況是一樣的潮改。

帶著上面幾種現(xiàn)象费奸,我們總結(jié)了以下3個問題??

  1. dispatch_group_enter dispatch_group_leave 還有group_notify底層源碼處理了哪些流程?
  2. dispatch_group_notify是如何接收dispatch_group_enterdispatch_group_leave傳遞的信息进陡,然后執(zhí)行block任務(wù)?
  3. 還有dispatch_group_async的底層源碼做了什么微服?
dispatch_group_create底層

我們先看看dispatch_group_create

dispatch_group_t
dispatch_group_create(void)
{
    return _dispatch_group_create_with_count(0);
}
dispatch_group_enter底層


看看os_atomic_sub_orig2o??

#define os_atomic_sub_orig2o(p, f, v, m) \
        os_atomic_sub_orig(&(p)->f, (v), m)

#define os_atomic_sub_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, sub, -)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

注意第4個入?yún)⒅壕危椭鞍l(fā)送信號量dispatch_semaphore_signal的宏定義處理基本一模一樣缨历,那么得到的拼接函數(shù)名是atomic_fetch_sub_explicit??

如果得到的old_value是0,那么接著進(jìn)入_dispatch_retain??

DISPATCH_ALWAYS_INLINE_NDEBUG
static inline void
_dispatch_retain(dispatch_object_t dou)
{
    (void)_os_object_retain_internal_n_inline(dou._os_obj, 1);
}
#define _os_object_refcnt_add_orig(o, n) \
        _os_atomic_refcnt_add_orig2o(o, os_obj_ref_cnt, n)

#define _os_atomic_refcnt_add_orig2o(o, m, n) \
        _os_atomic_refcnt_perform2o(o, m, add_orig, n, relaxed)

#define _os_atomic_refcnt_perform2o(o, f, op, n, m)   ({ \
        __typeof__(o) _o = (o); \
        int _ref_cnt = _o->f; \
        if (likely(_ref_cnt != _OS_OBJECT_GLOBAL_REFCNT)) { \
            _ref_cnt = os_atomic_##op##2o(_o, f, n, m); \
        } \
        _ref_cnt; \
    })

同樣的替換糙麦,得到os_atomic_add_orig2o

#define os_atomic_add_orig2o(p, f, v, m) \
        os_atomic_add_orig(&(p)->f, (v), m)

#define os_atomic_add_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, add, +)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

再次替換辛孵,得到atomic_fetch_add_explicit,又是這個加1操作赡磅。
至此驗證了魄缚,當(dāng)從os底層哈希表中獲取的當(dāng)前group的INTERVAL值為0時,走_dispatch_retain焚廊,會+1冶匹,類似于一個加鎖的操作,一直在此wait咆瘟。

dispatch_group_leave底層


接著看看 os_atomic_add_orig2o

#define os_atomic_add_orig2o(p, f, v, m) \
        os_atomic_add_orig(&(p)->f, (v), m)

#define os_atomic_add_orig(p, v, m) \
        _os_atomic_c11_op_orig((p), (v), m, add, +)

#define _os_atomic_c11_op_orig(p, v, m, o, op) \
        atomic_fetch_##o##_explicit(_os_atomic_c11_atomic(p), v, \
        memory_order_##m)

拼接函數(shù)名嚼隘,得到atomic_fetch_add_explicit --> +1操作,和注釋描述的一樣incremented -1 -> 0 transition袒餐。

下圖是一個特殊情況飞蛹,當(dāng)enter多次時的處理??

再接著看看_dispatch_group_wake

至此,我們知道了dispatch_group_enter 減一 dispatch_group_leave 加一灸眼,同時卧檐,當(dāng)從os底層獲取group的INTERVAL的值為DISPATCH_GROUP_VALUE_1時,會執(zhí)行一個do-while循環(huán)處理新舊狀態(tài)值焰宣,最終dispatch_group_leave會調(diào)用_dispatch_group_wake通知group任務(wù)block繼續(xù)往下執(zhí)行霉囚。

dispatch_group_notify底層

通過對dispatch_group_enterdispatch_group_leave的底層源碼分析得知,只有leave時宛徊,old_value才會從-1 --> 0佛嬉,所以dispatch_group_leave后,dispatch_group_notify才會調(diào)用_dispatch_group_wake通知group任務(wù)block繼續(xù)往下執(zhí)行闸天。這里也就回答了問題1問題2的原因所在暖呕。

dispatch_group_async的底層

最后看看dispatch_group_async的源碼??

_dispatch_continuation_async我們再熟悉不過,之前在分析dispatch_async時苞氮,調(diào)用棧里也有它??

接著dx_push湾揽,它會創(chuàng)建子線程,綁定任務(wù)_dispatch_worker_thread2笼吟,而_dispatch_worker_thread2就是負(fù)責(zé)調(diào)用group任務(wù)block的所在库物。

那么問題來了,_dispatch_continuation_async里有了dispatch_group_enter贷帮,卻沒有dispatch_group_leave戚揭,leave在哪里被調(diào)用呢?猜想撵枢,是否在任務(wù)block執(zhí)行完成后民晒?我們不妨驗證一下精居,在block里打斷點,bt查看其調(diào)用棧信息??

_dispatch_call_block_and_release這個明顯是block執(zhí)行完成潜必,release釋放了靴姿,所以往下看_dispatch_client_callout,這個我們也知道磁滚,是觸發(fā)任務(wù)block佛吓,再往下,看看_dispatch_queue_override_invoke??

通過bt查看調(diào)用棧信息垂攘,查找到_dispatch_queue_override_invoke里维雇,然后繼續(xù)往下查找,最終找到了dispatch_group_leave搜贤。

綜上我們可以得出結(jié)論??
dispatch_group_async = dispatch_group_enter + 子線程綁定谆沃,block任務(wù)調(diào)用 + dispatch_group_leave

以上就是對調(diào)度組dispatch_group_t幾個常用api的底層源碼的分析,我們現(xiàn)在已經(jīng)清楚的知道了dispatch_group_enterdispatch_group_leave 必須配對使用仪芒,而dispatch_group_notify的執(zhí)行唁影,必須依賴dispatch_group_leave的喚醒。同時掂名,dispatch_group_async就是簡化版的dispatch_group_enterdispatch_group_leave据沈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饺蔑,隨后出現(xiàn)的幾起案子锌介,更是在濱河造成了極大的恐慌,老刑警劉巖猾警,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孔祸,死亡現(xiàn)場離奇詭異,居然都是意外死亡发皿,警方通過查閱死者的電腦和手機崔慧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來穴墅,“玉大人惶室,你說我怎么就攤上這事⌒酰” “怎么了皇钞?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長松捉。 經(jīng)常有香客問我夹界,道長,這世上最難降的妖魔是什么隘世? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任可柿,我火速辦了婚禮也拜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘趾痘。我一直安慰自己,他們只是感情好蔓钟,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布永票。 她就那樣靜靜地躺著,像睡著了一般滥沫。 火紅的嫁衣襯著肌膚如雪侣集。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天兰绣,我揣著相機與錄音世分,去河邊找鬼。 笑死缀辩,一個胖子當(dāng)著我的面吹牛臭埋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臀玄,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼瓢阴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了健无?” 一聲冷哼從身側(cè)響起荣恐,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎累贤,沒想到半個月后叠穆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡臼膏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年硼被,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讶请。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡祷嘶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出夺溢,到底是詐尸還是另有隱情论巍,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布风响,位于F島的核電站嘉汰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏状勤。R本人自食惡果不足惜鞋怀,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一双泪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧密似,春花似錦焙矛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抛猫,卻和暖如春蟆盹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闺金。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工逾滥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人败匹。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓寨昙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哎壳。 傳聞我的和親對象是個殘疾皇子毅待,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361