回顾

在上篇博客已经对GCDsync同步函数产生死锁的情况,进行了底层的源码探索分析,那么本篇博客继续源码的探索分析!

iOS底层探索之多线程(一)—进程和线程

iOS底层探索之多线程(二)—线程和锁

iOS底层探索之多线程(三)—初识GCD

iOS底层探索之多线程(四)—GCD的队列

iOS底层探索之多线程(五)—GCD不同队列源码分析

iOS底层探索之多线程(六)—GCD源码分析(sync 同步函数、async 异步函数)

iOS底层探索之多线程(七)—GCD源码分析(死锁的原因)

1. 全局并发队列+同步函数

dq->dq_width == 1为串行队列,那么并发队列该怎么走呢?
如下图,走的是下面的框框中流程

但是这么多的分支,到底是走的哪一个呢?通过对_dispatch_sync_f_slow_dispatch_sync_recurse_dispatch_introspection_sync_begin_dispatch_sync_invoke_and_complete方法下符号断点,进行跟踪调试。

  • 符号断点调试


通过下符号断点跟踪,发现走了_dispatch_sync_f_slow,如下图所示:


通过阅读源码,发现一个有意思的事情,就是_dispatch_sync_invoke_and_complete方法

  • _dispatch_sync_invoke_and_complete

在这个_dispatch_sync_invoke_and_complete方法的第三个参数是func也是需要执行的任务,但是 func的后面的整体也是一个参数,也就是DISPATCH_TRACE_ARG( _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)) 整体为一个参数,这就有意思了,中间居然没有逗号分隔开。老铁,你这很特别啊!够长的啊!

那么去DISPATCH_TRACE_ARG定义看看

DISPATCH_TRACE_ARG的宏定义里面,你们有没有发现,这里居然把逗号放在了里面,好家伙,宏定义里面还可以这么玩,苹果工程师还真有意思哈!

通过全局的搜索,发现这个宏定义有两处,一个有逗号,一个没有逗号,这就是根据不同的条件,进行设置,相当于是一个可选的参数,这一波操作又是非常的细节了!

既然下符号断点会走_dispatch_sync_f_slow方法,现在就去看看这个方法

  • _dispatch_sync_f_slow


这里又是很多的分支,又通过下符号断点,发现走的是_dispatch_sync_function_invoke方法里面

  • _dispatch_sync_function_invoke
static void
_dispatch_sync_function_invoke(dispatch_queue_class_t dq, void *ctxt,dispatch_function_t func)
{_dispatch_sync_function_invoke_inline(dq, ctxt, func);
}
  • _dispatch_sync_function_invoke_inline
static inline void
_dispatch_sync_function_invoke_inline(dispatch_queue_class_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);
}
  • push 之后调用callout执行,最后再 pop,所以可以同步的执行任务

2. 异步函数

dispatch_async异步函数的任务,是包装在 qos里面的,那么现在跟踪流程,去看看

  • dispatch_async

  • _dispatch_continuation_async

  • dx_push


搜索dx_push调用的地方

这里就先去看看并发队列里面的dq_push吧,

  • _dispatch_lane_concurrent_push


这里if里面有对栅栏函数(_dispatch_object_is_barrier)的判断,栅栏函数这里就不分析了,后续的博客里面会分析的。

_dispatch_lane_concurrent_push里面会去调用_dispatch_lane_push方法,在上面搜索dx_push的图里面,可以看到,在串行队列里面是直接调用了_dispatch_lane_push,也就是说串行并发都会走这个方法。

  • _dispatch_lane_push


最后去调用dx_wakeup,再去搜索看看

dx_wakeup 是一个宏定义,看看dq_wakeup哪里调用了

如上图可以发现,串行和并发都是_dispatch_lane_wakeup,全局的是_dispatch_root_queue_wakeup

  • _dispatch_queue_wakeup

通过下符号断点会走_dispatch_lane_class_barrier_complete

_dispatch_lane_class_barrier_complete里面循环递归一些操作,还看到了一个系统的函数os_atomic_rmw_loop2o,在这个方法里面要么返回dx_wakeup或者做其他的一些处理。



#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)

通过跟流程和下符号断点,会走全局并发队列的_dispatch_root_queue_push方法。通过下符号断点,跟踪源码,最终定位到一个重要的方法_dispatch_root_queue_poke_slow

dispatch_root_queue_push_inline(dispatch_queue_global_t dq,dispatch_object_t _head, dispatch_object_t _tail, int n)
{struct dispatch_object_s *hd = _head._do, *tl = _tail._do;if (unlikely(os_mpsc_push_list(os_mpsc(dq, dq_items), hd, tl, do_next))) {return _dispatch_root_queue_poke(dq, n, 0);}
}
  • _dispatch_root_queue_poke

  • _dispatch_root_queue_poke_slow


_dispatch_root_queues_init方法使用了单例。

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

在该方法中,采用单例的方式进行了线程池的初始化处理、工作队列的配置、工作队列的初始化等工作。同时这里有一个关键的设置,执行函数的设置,也就是将任务执行的函数被统一设置成了_dispatch_worker_thread2。见下图:

  • 调用堆栈验证

调用执行是通过workloop工作循环调用起来的,也就是说并不是及时调用的,而是通过os完成调用,说明异步调用的关键是在需要执行的时候能够获取对应的方法,进行异步处理,而同步函数是直接调用。

在上面的流程中_dispatch_root_queue_poke_slow方法,还没有继续分析,现在就去分析,如果是全局队列,此时会创建线程进行执行任务

对线程池进行处理,从线程池中获取线程,执行任务,同时判断线程池的变化

remaining可以理解为当前可用线程数,当可用线程数等于0时,线程池已满pthread pool is full,直接return。底层通过pthread完成线程的开辟

就是_dispatch_worker_thread2是通过pthread完成oc_atmoic原子触发

那么我们的线程可以开辟多少线程条呢?

队列线程池的大小为:dgq_thread_pool_sizedgq_thread_pool_size = thread_pool_size ,默认大小如下:

255表示理论上线程池的最大数量。但是实际能开辟多少呢,这个不确定。在苹果官方完整Thread Management中,有相关的说明,辅助线程的最小允许堆栈大小为 16KB,并且堆栈大小必须是4KB 的倍数。见下图:

也就是说,一个辅助线程的栈空间是512KB,而一个线程所占用的最小空间是16KB,也就是说栈空间一定的情况下,开辟线程所需的内存越大,所能开辟的线程数就越小。针对一个4GB内存的iOS真机来说,内存分为内核态和用户态,如果内核态全部用于创建线程,也就是1GB的空间,也就是说最多能开辟1024KB / 16KB个线程。当然这也只是一个理论值。

3. 单例

上面提到了单例,那么接下来就去分析一下单例
来看看简单的单例使用:

   static dispatch_once_t token;dispatch_once(&token, ^{// 代码执行});
  • 单例的定义如下:
void
_dispatch_once(dispatch_once_t *predicate,DISPATCH_NOESCAPE dispatch_block_t block)
{if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {dispatch_once(predicate, block);} else {dispatch_compiler_barrier();}DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}
#undef dispatch_once
#define dispatch_once _dispatch_once
#endif
#endif // DISPATCH_ONCE_INLINE_FASTPATH

针对不同的情况作了一些特殊处理,比如栅栏函数等,这里只分析dispatch_once,进入dispatch_once实现

单例是只会执行一次,那么这里就是利用 val参数来进行控制的,接着去dispatch_once_f里面看看

l的底层原子性进行关联,关联到uintptr_t v的一个变量,通过os_atomic_load从底层取出,关联到变量v上。如果v这个值等于DLOCK_ONCE_DONE,也就是已经处理过一次了,就会直接return返回

  • _dispatch_once_gate_tryenter
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}

_dispatch_once_gate_tryenter里面是进行原子操作,就是锁的处理,如果之前没有执行过,原子处理会比较它状态,进行解锁,最终会返回一个bool值,多线程情况下,只有一个能够获取锁返回yes

if (_dispatch_once_gate_tryenter(l)) {return _dispatch_once_callout(l, ctxt, func);}

通过_dispatch_lock_value_for_self上了一把锁,保证多线程安全。如果返回yes,就会执行_dispatch_once_callout方法,执行单例对应的任务,并对外广播

  • _dispatch_once_callout
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,dispatch_function_t func)
{_dispatch_client_callout(ctxt, func);_dispatch_once_gate_broadcast(l);
}
  • _dispatch_client_callout执行任务

  • _dispatch_once_gate_broadcast对外广播,标记为 done

  • _dispatch_once_gate_broadcast广播


token通过原子比对,如果不是done,则设为done。同时对_dispatch_once_gate_tryenter方法中的锁进行处理。

  • _dispatch_once_mark_done


os_atomic_cmpxchg是一个宏定义,先进行比较再改变,先比较 dgo,在设置标记为DLOCK_ONCE_DONE也就是done

token标记为done之后,就会直接返回,如存在多线程处理,没有获取锁的情况,就会调用_dispatch_once_wait,如下下:

_dispatch_once_wait,进行等待,这里开启了自旋锁,内部进行原子处理,在loop过程中,如果发现已经被其他线程设置once_done了,则会进行放弃处理

那么任务的执行交给谁了呢?

通过打印堆栈信息,发现是交给了下层的线程,通过一些包装,给了底层的pthread

这就可以说 GCD底层是封装了pthread,不管是iOS还是 Java都是封装了底层的通用线程机制pthread

这里的执行是通过工作循环workloop,工作循环的调起受 OS(受 CPU调度执行的)管控的,异步线程的异步体现在哪里呢?就是体现在是否可以获得,而不是立即执行,而同步函数是直接调用执行的,而这里并没有看到异步的直接调用执行。

更多内容持续更新

iOS底层探索之多线程(八)—GCD源码分析(函数的同步性、异步性、单例)相关推荐

  1. 多线程:AQS源码分析

    AQS 源码分析 概述 Java的内置锁一直都是备受争议的,在JDK 1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略,但是与Lock相比s ...

  2. Java多线程 -- JUC包源码分析2 -- Copy On Write/CopyOnWriteArrayList/CopyOnWriteArraySet

    本人新书出版,对技术感兴趣的朋友请关注: https://mp.weixin.qq.com/s/uq2cw2Lgf-s4nPHJ4WH4aw 上1篇讲述了Java并发编程的第1个基本思想–CAS/乐观 ...

  3. concurrent(六)同步辅助器CyclicBarrier 源码分析

    参考文档: Java多线程系列--"JUC锁"10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/35339 ...

  4. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  5. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  6. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  7. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  8. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  9. 【Netty源码分析摘录】(八)新连接的接入

    文章目录 1.问题 2.检测新连接接入 3.创建客户端 channel 4. 绑定 NioEventLoop 4.1 register0 4.1.1 doRegister() 4.1.2 pipeli ...

  10. spark 源码分析之十八 -- Spark存储体系剖析

    本篇文章主要剖析BlockManager相关的类以及总结Spark底层存储体系. 总述 先看 BlockManager相关类之间的关系如下: 我们从NettyRpcEnv 开始,做一下简单说明. Ne ...

最新文章

  1. poj1738 an old stone game
  2. Promise详解(一) ----基础用法
  3. c语言编程中%g是什么格式
  4. 超萌哒!20000个虎年限定红包封面,限量开抢!
  5. 两个sql交集_简单明了的sql基础语句
  6. python写小猪佩奇_【python】使用python自动画一只小猪佩奇(源码)-Go语言中文社区...
  7. 【git】Git 提示fatal: remote origin already exists 错误解决办法
  8. 上海智能网联汽车规模化示范应用启动 滴滴上线自动驾驶服务
  9. 【动态规划】P1220:区间dp:关路灯
  10. Python基础090:解决jupyter notebook无法自动跳转chrome浏览器的问题
  11. 使用PGP加密你的文件
  12. 怎么复制黑苹果config配置_只需3步,实现黑苹果USB端口配置
  13. LSB信息隐藏的卡方分析
  14. DDD结合整洁架构落地实践
  15. SQL 用户行为路径分析
  16. 什么?HomeKit、米家、Aqara等生态也能通过智汀与天猫精灵生态联动?
  17. 云呐容灾备份策略,存储容灾备份系统
  18. ​微信小程序开发难?资深大V教您微信小程序制作步骤和方法​
  19. Skywalking光会用可不行,必须的源码分析分析 - Skywalking Agent 插件解析
  20. 存储程序的概念将计算机转换为,2015计算机二级office高级应用模拟题及答案(1)...

热门文章

  1. 《HelloGitHub》第 24 期(两周年)
  2. 点击某些按钮不要触发验证控件
  3. py2exe将python打包成exe
  4. 微软 .NET 俱乐部在线发布会
  5. SQL*Loader 和 Data Pump
  6. 实战(一):对“钉钉”的逆向(实现打卡功能)
  7. PHP基础--PDO的常用操作
  8. 【转】mybatis在xml文件中处理大于号小于号的方法
  9. Java知多少(64)线程死锁
  10. ZooKeeper(3.4.5) - 原生 API 的简单示例