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;
}
image.png

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


image.png

image.png

image.png

image.png

2.pthread创建线程过程

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


image.png

image.png

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

单例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
image.png

image.png

image.png

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

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


    image.png
  • 2.同步栅栏函数:会堵塞当前所在线程后面语句


    image.png
  • 3.栅栏函数传入的只能是并发队列,且只能是自定义的并发队列不能是全局并发队列

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


    image.png

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


    image.png

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


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


    image.png

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

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

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


image.png

死锁堆栈中信息_dispatch_sync_f_slow


image.png

当前任务要执行又要等待,处于矛盾中,会死锁
image.png
image.png

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

7.信号量分析

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


image.png

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


image.png

image.png

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

推荐阅读更多精彩内容