GCD源码解析

GCD源码解析

在我们开发过程中,我们使用到多线程的时候,最多的应该是使用GCD,因为它使用起来非常的简洁,简单。
平常中,我们使用最多的,
dispatch_queue_create,
dispatch_get_global_queue,
dispatch_sync,
dispatch_async,
dispatch_group_create,
dispatch_once等。可是,我们是否知道这些背后是做了什么事情,它和我们的内部线程到底是什么关系,或者,他们直接是否有直接的关系?我依次来做解答

dispatch_sync

dispatch_syncdispatch_async是相对的,一个是同步任务,需要等待任务完成,一个异步不等待任务完成就返回。两种不同的方式,首先我们先介绍dispatch_sync.

dispatch_sync: 它一般情况下是不会重启一个新的线程,而是在当前线程执行,注意,这里说的是当前线程,而不是主线程。当然里面有特殊的存在get_main_queue,它是在主线程执行的。当我们调用dispatch_sync,也就是同步任务的时候,是分两种队列的,一种就是串行队列,一种是并发队列。在我们解析dispatch_sync源码的之前,我们先写几个例子,并且,把调用栈给写出来,这样更加有利于我们分析源码。

// 串行队列同步执行
dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_SERIAL);//创建的是串行队列
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });

// 下面是运行结果
 myDemo[41725:1739115] current thread = <NSThread: 0x60400006cb80>{number = 1, name = main}
 myDemo[41725:1739115] 111
 myDemo[41725:1739115] current thread = <NSThread: 0x60400006cb80>{number = 1, name = main}
 myDemo[41725:1739115] 222

首先我们把此demo的堆栈贴出来


image.png

通过这个demo,就是按照我们所理解的串行执行,线程也是在主线程,因为我们本身就是默认在主线程运行,就像上面讲的:dispatch_sync只是在当前线程执行

// 并行队列的同步执行
dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
        [NSThread sleepForTimeInterval:5];
    });
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });
    // 运行结果
     myDemo[82272:3241865] current thread = <NSThread: 0x60000006f900>{number = 1, name = main}
     myDemo[82272:3241865] 111
     myDemo[82272:3241865] current thread = <NSThread: 0x60000006f900>{number = 1, name = main}
     myDemo[82272:3241865] 222

堆栈队列


image.png

通过这个demo,我可以看到,即便是用的并行队列,依然还是在主线程,并没有重启一个新的线程,并且是串行执行。
说明,我一开始总结的是对的,执行dispatch_sync,并没有重启线程,即便是并行队列。下面再看其他的demo

dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_SERIAL);// 创建串行队列
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
        dispatch_sync(queue01, ^{
            NSLog(@"current thread = %@", [NSThread currentThread]);
            NSLog(@"222");
        });
    });
    // 此处就会死锁造成crash.
    
    // 我们来看另外一个例子
dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_CONCURRENT);// 创建并行队列
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
        dispatch_sync(queue01, ^{
            NSLog(@"current thread = %@", [NSThread currentThread]);
            NSLog(@"222");
        });
    });
    // 执行结果
myDemo[84905:3394929] current thread = <NSThread: 0x600000262e00>{number = 1, name = main}
myDemo[84905:3394929] 111
myDemo[84905:3394929] current thread = <NSThread: 0x600000262e00>{number = 1, name = main}
myDemo[84905:3394929] 222
// 执行结果依然在主线程

通过这两个例子的比较,只有在同步执行,串行队列的情况下,才会造成死锁。使用并行队列的时候,同步执行,依然没问题,并且依然在主线程。下面,我们上面那个例子的堆栈信息截屏下来


image.png
    // 不是通过create queue的方式,创建全局的并行队列
    dispatch_queue_t queue01 = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });
    // 执行结果
    myDemo[1637:4224206] current thread = <NSThread: 0x604000076700>{number = 1, name = main}
    myDemo[1637:4224206] 222

这种方式执行的是_dispatch_sync_function_invoke
堆栈:

image.png

我根据上面3中堆栈信息,来分析dispatch_sync源码执行,解答为什么如此执行

dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_private_data(dq, work, 0);
    }
    dispatch_sync_f(dq, work, _dispatch_Block_invoke(work));
}

DISPATCH_NOINLINE
void
dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func)
{
    if (likely(dq->dq_width == 1)) {
        // 串行队列执行到这里
        return dispatch_barrier_sync_f(dq, ctxt, func);
    }

    // Global concurrent queues and queues bound to non-dispatch threads
    // always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_reserve_sync_width(dq))) {
    // 通过下面的堆栈,当创建全局并行队列的时候,才会执行到此方法
        return _dispatch_sync_f_slow(dq, ctxt, func, 0);
    }

    _dispatch_introspection_sync_begin(dq);
    if (unlikely(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dq, ctxt, func, 0);
    }
    // 通过上面并行队列堆栈发现dispatch_queue_create创建的并且队列,会调用此方法
    _dispatch_sync_invoke_and_complete(dq, ctxt, func);
}

DISPATCH_NOINLINE
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func)
{
    dispatch_tid tid = _dispatch_tid_self();

    // The more correct thing to do would be to merge the qos of the thread
    // that just acquired the barrier lock into the queue state.
    //
    // However this is too expensive for the fastpath, so skip doing it.
    // The chosen tradeoff is that if an enqueue on a lower priority thread
    // contends with this fastpath, this thread may receive a useless override.
    //
    // Global concurrent queues and queues bound to non-dispatch threads
    // always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dq, tid))) {
        return _dispatch_sync_f_slow(dq, ctxt, func, DISPATCH_OBJ_BARRIER_BIT);
    }

    _dispatch_introspection_sync_begin(dq);
    if (unlikely(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dq, ctxt, func, DISPATCH_OBJ_BARRIER_BIT);
    }
    // 串行队列的时候,执行此方法
    _dispatch_queue_barrier_sync_invoke_and_complete(dq, ctxt, func);
}

通过上面串行队列的堆栈,我看到执行到此方法_dispatch_queue_barrier_sync_invoke_and_complete

DISPATCH_NOINLINE
static void
_dispatch_queue_barrier_sync_invoke_and_complete(dispatch_queue_t dq,
        void *ctxt, dispatch_function_t func)
{
    // 首先先执行此方法
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    if (unlikely(dq->dq_items_tail || dq->dq_width > 1)) {
        return _dispatch_queue_barrier_complete(dq, 0, 0);
    }

    // Presence of any of these bits requires more work that only
    // _dispatch_queue_barrier_complete() handles properly
    //
    // Note: testing for RECEIVED_OVERRIDE or RECEIVED_SYNC_WAIT without
    // checking the role is sloppy, but is a super fast check, and neither of
    // these bits should be set if the lock was never contended/discovered.
    const uint64_t fail_unlock_mask = DISPATCH_QUEUE_SUSPEND_BITS_MASK |
            DISPATCH_QUEUE_ENQUEUED | DISPATCH_QUEUE_DIRTY |
            DISPATCH_QUEUE_RECEIVED_OVERRIDE | DISPATCH_QUEUE_SYNC_TRANSFER |
            DISPATCH_QUEUE_RECEIVED_SYNC_WAIT;
    uint64_t old_state, new_state;

    // similar to _dispatch_queue_drain_try_unlock
    os_atomic_rmw_loop2o(dq, dq_state, old_state, new_state, release, {
        new_state  = old_state - DISPATCH_QUEUE_SERIAL_DRAIN_OWNED;
        new_state &= ~DISPATCH_QUEUE_DRAIN_UNLOCK_MASK;
        new_state &= ~DISPATCH_QUEUE_MAX_QOS_MASK;
        if (unlikely(old_state & fail_unlock_mask)) {
            os_atomic_rmw_loop_give_up({
                return _dispatch_queue_barrier_complete(dq, 0, 0);
            });
        }
    });
    if (_dq_state_is_base_wlh(old_state)) {
        _dispatch_event_loop_assert_not_owned((dispatch_wlh_t)dq);
    }
}

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func)
{
    dispatch_thread_frame_s dtf;
    _dispatch_thread_frame_push(&dtf, dq);
    // 在我们的串行队列中,最终执行到此方法
    _dispatch_client_callout(ctxt, func);
    _dispatch_perfmon_workitem_inc();
    _dispatch_thread_frame_pop(&dtf);
}

并行队列串行执行的时候,会执行_dispatch_sync_invoke_and_complete

DISPATCH_NOINLINE
static void
_dispatch_sync_invoke_and_complete(dispatch_queue_t dq, void *ctxt,
        dispatch_function_t func)
{
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_queue_non_barrier_complete(dq);
}

最终依然会执行到_dispatch_client_callout
通过上面的源码的分析,在执行dispatch_sync的时候,并不会创建新的线程。

dispatch_async

dispatch_async运用了底层线程池,会在和当前线程不同的线程上处理任务。同一个串行队列在异步执行,会在同一个线程中。不同的串行队列,异步执行,才会重启一个线程。但是并行队列,每次都会重启一个线程。
首先,我依然还是通过几个例子,还看看什么效果,并且调出他们的堆栈.

// 串行队列异步执行
dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });

// 执行结果

myDemo[38842:5876694] current thread = <NSThread: 0x600000278b40>{number = 3, name = (null)}
myDemo[38842:5876694] 111

调用堆栈:


image.png
// 异步队列异步执行
dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });

// 执行结果
myDemo[39184:5895013] current thread = <NSThread: 0x604000475780>{number = 3, name = (null)}
myDemo[39184:5895013] 111

调用堆栈:


image.png
// 创建全局异步队列
dispatch_queue_t queue01 = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });

// 执行结果
myDemo[39245:5898319] current thread = <NSThread: 0x600000466640>{number = 3, name = (null)}
myDemo[39245:5898319] 111

调用堆栈:


image.png

通过上面的代码显示,无论是我们创建的时候串行队列,还是异步队列,或者是全局队列,只要异步执行,都会创建新的线程,只不过调用堆栈,有些许不同,后面,我们讲解代码的时候,会根据堆栈来讲解。
下面,我来看一下,其他的情况:

dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_SERIAL);
 dispatch_async(queue01, ^{
        sleep(5);
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });
    
// 执行结果
myDemo[39545:5914239] current thread = <NSThread: 0x60000027cb40>{number = 3, name = (null)}
myDemo[39545:5914239] 111
myDemo[39545:5914239] current thread = <NSThread: 0x60000027cb40>{number = 3, name = (null)}
myDemo[39545:5914239] 222

同一个串行队列异步执行,会在同一个异步线程中,串行执行

dispatch_queue_t queue01 = dispatch_queue_create("queue01", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue01, ^{
        sleep(5);
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });
// 执行结果

myDemo[39642:5919537] current thread = <NSThread: 0x600000265bc0>{number = 3, name = (null)}
myDemo[39642:5919537] 222
myDemo[39642:5919535] current thread = <NSThread: 0x60000007e700>{number = 4, name = (null)}
myDemo[39642:5919535] 111

同一个并行队列,异步执行,会分别生成两个不同的线程。相当于是每一次的并行队列,异步执行,都会重启一个线程执行

// 全局队列
dispatch_queue_t queue01 = dispatch_get_global_queue(0, 0);
dispatch_async(queue01, ^{
        sleep(5);
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"111");
    });
    dispatch_async(queue01, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"222");
    });
// 执行结果
myDemo[39753:5926161] current thread = <NSThread: 0x6040002656c0>{number = 3, name = (null)}
myDemo[39753:5926161] 222
myDemo[39753:5926162] current thread = <NSThread: 0x600000460fc0>{number = 4, name = (null)}
myDemo[39753:5926162] 111

可以上面的是同样的结果。

异步源码的分析

通过上面的串行队列异步执行的堆栈,我们清楚分别执行:

_dispatch_queue_push->start_wqthread->_dispatch_call_block_and_release

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

    _dispatch_continuation_init(dc, dq, work, 0, 0, dc_flags);
    _dispatch_continuation_async(dq, dc);
}

持续更新。。。。

GitHub地址:https://github.com/BernardChina/BCStudy/blob/master/GCD%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.md

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,961评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,444评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,009评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,082评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,101评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,271评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,738评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,395评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,539评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,434评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,481评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,160评论 3 317
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,749评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,816评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,038评论 1 256
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,548评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,140评论 2 341

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,281评论 8 265
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,723评论 1 17
  • iOS多线程编程 基本知识 1. 进程(process) 进程是指在系统中正在运行的一个应用程序,就是一段程序的执...
    陵无山阅读 5,989评论 1 14
  • 我喜欢你 当星期一和星期四的下课铃响起 我的微笑就禁不住要洋溢 像小鸟张开了翅膀 像白云舒展开双臂 我喜欢你 当一...
    竹子LHM阅读 366评论 2 3
  • “走在乡间的小路上,暮归的老牛是我同伴,蓝天配朵夕阳在胸膛,缤纷的云彩是晚霞的衣裳……”每次听到这首《乡间的小路》...
    梦在雨巷阅读 409评论 1 1