iOS-底层原理25-GCD(下)

《iOS底层原理文章汇总》
上一篇文章《iOS-底层原理24-GCD(上)》介绍了异步函数disasync的包装和调用流程,本文介绍线程是怎么被GCD封装创建的

1.队列的创建以模板进行处理:基础模板的基础上进行修改

DISPATCH_ALWAYS_INLINE
static inline dispatch_introspection_queue_s
_dispatch_introspection_lane_get_info(dispatch_lane_class_t dqu)
{
    dispatch_lane_t dq = dqu._dl;
    bool global = _dispatch_object_is_global(dq);
    uint64_t dq_state = os_atomic_load2o(dq, dq_state, relaxed);

    dispatch_introspection_queue_s diq = {
        .queue = dq->_as_dq,
        .target_queue = dq->do_targetq,
        .label = dq->dq_label,
        .serialnum = dq->dq_serialnum,
        .width = dq->dq_width,
        .suspend_count = _dq_state_suspend_cnt(dq_state) + dq->dq_side_suspend_cnt,
        .enqueued = _dq_state_is_enqueued(dq_state) && !global,
        .barrier = _dq_state_is_in_barrier(dq_state) && !global,
        .draining = (dq->dq_items_head == (void*)~0ul) ||
                (!dq->dq_items_head && dq->dq_items_tail),
        .global = global,
        .main = dx_type(dq) == DISPATCH_QUEUE_MAIN_TYPE,
    };
    return diq;
}
iOS-底层原理25-GCD(下)_第1张图片
image.png

怎么知道如下进入_dispatch_continuation_redirect_push还是_dispatch_lane_push,通过下符号断点的方式知道进入_dispatch_continuation_redirect_push之后进入_dispatch_root_queue_push


iOS-底层原理25-GCD(下)_第2张图片
image.png

image.png

image.png

iOS-底层原理25-GCD(下)_第3张图片
image.png

2.pthread创建线程过程

以上程序进入_dispatch_root_queue_push后进入_dispatch_root_queue_push_inline(rq, dou, dou, 1)符号断点验证进入_dispatch_root_queue_poke_slow


iOS-底层原理25-GCD(下)_第4张图片
image.png

image.png

3.GCD单例:流程如何控制一次?怎么保证线程安全?

iOS-底层原理25-GCD(下)_第5张图片
单例dispatch_once.png

线程安全,第一次还没访问到这把锁,第二次又进入会进入等待状态,始终返回不了,可以对延迟做设置_dispatch_once_wait(l)

4.上节课的堆栈信息中pthread创建线程后libsystem_pthread.dylib_pthread_wqthread,怎么调用到libdispatch.dylib_dispatch_worker_thread2的呢?

在_dispatch_root_queues_init_once方法中注册_dispatch_worker_thread2,经过系统级别的os_atomic_load2o(dq, dgq_thread_pool_size, ordered)进行调用,之后还有回调completeHandler

* thread #4, queue = 'cooci', stop reason = breakpoint 1.1
  * frame #0: 0x000000010b60b157 001---函数与队列`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x000000010b60e0e8) at ViewController.m:42:9
    frame #1: 0x000000010b876f11 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #2: 0x000000010b877e8e libdispatch.dylib`_dispatch_client_callout + 8
    frame #3: 0x000000010b87a7a3 libdispatch.dylib`_dispatch_continuation_pop + 552
    frame #4: 0x000000010b879bbb libdispatch.dylib`_dispatch_async_redirect_invoke + 771
    frame #5: 0x000000010b889399 libdispatch.dylib`_dispatch_root_queue_drain + 351
    frame #6: 0x000000010b889ca6 libdispatch.dylib`_dispatch_worker_thread2 + 135
    frame #7: 0x00007fff523019f7 libsystem_pthread.dylib`_pthread_wqthread + 220
    frame #8: 0x00007fff52300b77 libsystem_pthread.dylib`start_wqthread + 15
iOS-底层原理25-GCD(下)_第6张图片
image.png

image.png

iOS-底层原理25-GCD(下)_第7张图片
image.png

5.栅栏函数dispatch_barrier_async(异步栅栏函数不会堵塞当前所在线程后面语句)和dispatch_barrier_sync(同步栅栏函数会堵塞当前所在线程后面语句),堵塞传入的队列(只能是自定义并发队列),直到前面的任务执行完,后面的栅栏函数中的block才会执行

  • 1.异步栅栏函数:不会堵塞当前所在线程后面语句


    iOS-底层原理25-GCD(下)_第8张图片
    image.png
  • 2.同步栅栏函数:会堵塞当前所在线程后面语句


    iOS-底层原理25-GCD(下)_第9张图片
    image.png
  • 3.栅栏函数传入的只能是并发队列,且只能是自定义的并发队列不能是全局并发队列

  • 4.并发队列数据安全问题,可以使用栅栏函数
    程序奔溃的原因是:self.mArray中不断加入新元素时,会不断set设置新值,retain新值,release旧值,多线程进入时,可能会释放还没有分配内存空间的指针地址,OC可变数组底层也是一个数组,数组大小是固定的10,在add到一定数量后,它就需要扩容,扩容会创建一个新的数组,把旧数组的数据移到新数组,旧的数组就被释放了,然后新的数组再指向给self.mArray,但是异步并发的情况下,其它任务中拿到的还是旧的已经被释放的数组指针,所以在它要扩容时想要释放这个旧的指针会报这个错,因为旧指针已经被释放了


    iOS-底层原理25-GCD(下)_第10张图片
    image.png

    若将数组self.mArray容量改为500就不会崩溃


    iOS-底层原理25-GCD(下)_第11张图片
    image.png

    为了保障数据安全,必须要给数据的存储加锁@synchronized(self){},确保操作数据的原子性,通过栅栏函数保障数据安全
    iOS-底层原理25-GCD(下)_第12张图片
    image.png
  • 5.栅栏函数不能用全局并发队列,全局并发队列为系统的,往里面添加栅栏会挡住系统的任务,当前全局并发队列还有系统中的很多任务在后台执行


    iOS-底层原理25-GCD(下)_第13张图片
    image.png
  • 6.栅栏函数也无必要用串行队列,DISPATCH_QUEUE_SERIAL已经起到同步有序的作用了,队列里面的任务会依次有序执行,无需添加栅栏函数,只需添加异步函数即可,添加栅栏函数会浪费性能,底层会有栅栏相关的流程分析以及判别


    iOS-底层原理25-GCD(下)_第14张图片
    image.png

    栅栏在某一时刻只有一次,数组添加前判断前面是否有同步或栅栏函数拦截,没有则添加add,下次再进入,若发现有栅栏则无法进入进行add,有栅栏说明上一次任务没有执行完毕,这一次任务不会进入,只会栅栏一次

6.同步函数,栅栏函数,死锁底层源码分析

1.同步函数dispatch_sync,栅栏函数dispatch_barrier_async底层原理
2.模拟死锁:同步函数堵塞主线程


image.png

死锁堆栈中信息_dispatch_sync_f_slow


iOS-底层原理25-GCD(下)_第15张图片
image.png

当前任务要执行又要等待,处于矛盾中,会死锁
iOS-底层原理25-GCD(下)_第16张图片
image.png
iOS-底层原理25-GCD(下)_第17张图片
image.png

死锁只需判断当前要执行的任务和要等待的任务是不是同一个任务,而不管是在主线程还是子线程中都有可能发生死锁

7.信号量分析

很多时候GCD控制并发数比较难,可以通过设置信号量控制最大并发数,信号量设置为2,任务两两执行


iOS-底层原理25-GCD(下)_第18张图片
image.png

若信号量设置为1,则任务一个一个执行,起到同步的效果


image.png

iOS-底层原理25-GCD(下)_第19张图片
image.png

iOS-底层原理25-GCD(下)_第20张图片
image.png

你可能感兴趣的:(iOS-底层原理25-GCD(下))