前言
之前,咱们在探索动画及烘托相关原理的时分,咱们输出了几篇文章,解答了
iOS动画是怎么烘托,特效是怎么作业的疑惑
。咱们深感体系设计者在创作这些体系结构的时分,是如此脑洞大开,也深深意识到了解一门技能的底层原理关于从事该方面作业的重要性。
因此咱们决议
进一步探求iOS底层原理的使命
。继上一篇文章对GCD
的dispatch_get_global大局并发行列_queue+dispatch_sync同步函数
、dispatch_get_global大局并发行列_queue+dispatch_sync异步函数
、GCD单例
、GCD线程死锁
探索之后,本篇文章将持续对GCD多线程底层原理的探索
一、栅门函数基本介绍dispatch_barrier_async与dispatch_barrier_sync
1.1 栅门函数的作用
栅门函数的作⽤:
最直接的作⽤:
操控使命执⾏次序
,也便是达到同步的作用
-
dispatch_barrier_async
:前面的使命履行完毕,才会来到这儿 -
dispatch_barrier_sync
:作用相同,可是这个会堵塞线程,影响后边的履行
留意
:栅门函数只能操控同一并发行列
1.2 栅门函数运用举例
-
dispatch_barrier_async
举例
- 运转成果如下:
- 在同一个行列里边,
栅门函数
前面的使命履行完了,栅门函数里边的使命能够履行,可是不会堵塞线程
。 - 栅门函数后边的使命仍是能够履行的。可是栅门函数前面的使命,是一定在栅门函数内部使命之前履行的。
也便是
使命 1
和使命 2
是必然在栅门函数前面履行。
-
dispatch_barrier_sync
:
代码仍是👆上面的代码,便是把栅门函数
的异步
改成同步
了,看看会发生什么样的作用?
- 操控台打印成果如下:
- 栅门函数前面的使命仍是正常履行了,可是后边的使命在栅门函数的后边履行
- 栅门函数堵塞了线程,栅门函数后边的使命在栅门函数的使命履行完结,才会去履行
还记得上面的一句话吗:
栅门函数只能操控同一并发行列
,那么咱们试试不是同一个并发行列状况,栅门函数是否能够拦截住呢?
咱们把栅门函数
放在了另一个并发的行列里边,发现并没有拦截
住使命的履行,那么是不是异步
的原因呢?
那么现在去改成同步
看看能不能拦住呢?
从运转的成果来看,发现仍是拦不住,阐明不是同一个并发的行列,不论栅门函数是不是同步或者异步,都是拦截不住的,只能是同一个并发行列才能够!
咱们再来举个比如🌰,运用大局并发行列
看看
从打印成果来看,大局并发行列
也是拦不住的,只能是自定义
的并发行列
才能够,这是为什么呢?去底层源码看看是否能够找到答案!
二、 栅门函数源码剖析
2.1 流程盯梢
上面现已对栅门函数
的作用有一个大致的知道,那么底层的完结逻辑是怎么样的呢?现在就去探索一下。
在源码里边搜索dispatch_barrier_sync
,跟流程会走到_dispatch_barrier_sync_f
— > _dispatch_barrier_sync_f_inline
这个_dispatch_barrier_sync_f_inline
办法咱们之前剖析死锁
的时分来过这儿边,经过符号断点,这儿会走_dispatch_sync_f_slow
办法,这儿设置了DC_FLAG_BARRIER
的标签,对栅门做符号!
这儿也是之前同步发生死锁的时分来过的,经过下符号断点持续盯梢流程。
由此盯梢的流程为:_dispatch_sync_f_slow
–> _dispatch_sync_invoke_and_complete_recurse
–> _dispatch_sync_complete_recurse
,持续在源码里边盯梢发现定位到了这个_dispatch_sync_complete_recurse
办法。
这儿是一个 do while
循环,判别当时行列里边是否有barrier
,有的话就dx_wakeup
唤醒履行,直到使命履行完结了,才会履行_dispatch_lane_non_barrier_complete
,表明当时行列使命现已履行完结了,而且没有栅门函数了就会持续往下面的流程走。
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
那么现在去看看dq_wakeup
这儿咱们之前剖析同步和异步的时分也来过这儿,这儿大局并发的是 _dispatch_root_queue_wakeup
,串行和并发的是_dispatch_lane_wakeup
,那么两者有什么不一样呢?
2.2 自定义的并发行列剖析
咱们先去看看自定义的并发行列的_dispatch_lane_wakeup
_dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,
dispatch_wakeup_flags_t flags)
{
dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {
return _dispatch_lane_barrier_complete(dqu, qos, flags);
}
if (_dispatch_queue_class_probe(dqu)) {
target = DISPATCH_QUEUE_WAKEUP_TARGET;
}
return _dispatch_queue_wakeup(dqu, qos, flags, target);
}
- 判别是否为
barrier
方式的,会调用_dispatch_lane_barrier_complete
办法处理 - 假如没有
barrier
方式的,则走正常的并发行列流程,调用_dispatch_queue_wakeup
办法。 - _dispatch_lane_barrier_complete
- 假如是串行行列,则会进行等候,等候其他的使命履行完结,再按次序履行
- 假如是并发行列,则会调用
_dispatch_lane_drain_non_barriers
办法将栅门之前的使命履行完结。 - 最终会调用
_dispatch_lane_class_barrier_complete
办法,也便是把栅门拔掉了,不拦了,从而履行栅门之后的使命。
2.3 大局并发行列剖析
- 大局并发行列,
dx_wakeup
对应的是_dispatch_root_queue_wakeup
办法,检查源码完结
void
_dispatch_root_queue_wakeup(dispatch_queue_global_t dq,
DISPATCH_UNUSED dispatch_qos_t qos, dispatch_wakeup_flags_t flags)
{
if (!(flags & DISPATCH_WAKEUP_BLOCK_WAIT)) {
DISPATCH_INTERNAL_CRASH(dq->dq_priority,
"Don't try to wake up or override a root queue");
}
if (flags & DISPATCH_WAKEUP_CONSUME_2) {
return _dispatch_release_2_tailcall(dq);
}
}
- 大局并发行列这个里边,并没有对
barrier
的判别和处理,便是依照正常的并发行列来处理。 - 大局并发行列为什么没有对栅门函数进行处理呢?由于大局并发行列除了被咱们运用,体系也在运用。
- 假如添加了栅门函数,会导致行列运转的堵塞,从而影响体系级的运转,所以栅门函数也就不适用于大局并发行列。
三、 信号量dispatch_semaphore
3.1 信号量介绍
信号量在GCD
中是指Dispatch Semaphore
,是一种持有计数的信号的东西。有如下三个办法。
-
dispatch_semaphore_create
: 创建信号量 -
dispatch_semaphore_wait
: 信号量等候 -
dispatch_semaphore_signal
: 信号量开释
3.2 信号量举例
在并发行列里边,能够运用信号量操控,最大并发数,如下代码:
- 信号量举例打印成果
这儿一共创建了 4
个使命,异步并发履行,我在创建信号量的时分,设置了最大并发数为 2
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(2);
从运转的动图,能够看到,每次都是两个使命一同履行了,打印的成果一目了然。
那么再举个比如看看,设置信号量并发数为0
会怎么样呢?
设置信号量并发数为0
,就相当于加锁的作用,dispatch_semaphore_wait
堵住了使命1
让其等候,等使命 2
履行完了,dispatch_semaphore_signal
发送信号,我履行完了,你去履行吧!
这样到底信号量是怎么样等候,又是怎么样发送信号的呢?
3.3 信号量剖析
看看dispatch_semaphore_create
的 api
的阐明
- 当两个线程需求和谐特定事情的完结时,为该值传递
0
很有用。 - 传递大于
0
的值关于管理有限的资源池很有用,其间池大小等于该值。 - 信号量的起始值。 传递小于
信号量的起始值。 传递小于零的值将导致回来 NULL。
的值将导致回来NULL
,也便是小于0
就不会正常履行。
总结来说,便是能够操控线程池中的最多并发数量
3.3.1 dispatch_semaphore_signal
dispatch_semaphore_signal
- 在
dispatch_semaphore_signal
里边os_atomic_inc2o
原子操作自添加1
,然后会判别,假如value > 0
,就会回来0
。 - 例如
value
加1
之后仍是小于0
,阐明是一个负数
,也便是调用dispatch_semaphore_wait
次数太多了,dispatch_semaphore_wait
是做减操作的,等会后边会剖析。 - 加一次后依然小于
0
就报反常"Unbalanced call to dispatch_semaphore_signal()
,然后会调用_dispatch_semaphore_signal_slow
办法的,做容错的处理,_dispatch_sema4_signal
是一个do while
循环
_dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema)
{
_dispatch_sema4_create(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
_dispatch_sema4_signal(&dsema->dsema_sema, 1);
return 1;
}
- _dispatch_sema4_signal
void
_dispatch_sema4_signal(_dispatch_sema4_t *sema, long count)
{
do {
int ret = sem_post(sema);
DISPATCH_SEMAPHORE_VERIFY_RET(ret);
} while (--count);
}
3.3.2 dispatch_semaphore_wait
dispatch_semaphore_wait
dispatch_semaphore_wait
源码如下:
-
os_atomic_dec2o
进行原子自减1
操作,也便是对value
值进行减操作,操控可并发数。 - 假如可并发数为
2
,则调用该办法后,变为1
,表明现在并发数为1
,剩下还可一起履行1
个使命。假如初始值是0
,减操作之后为负数
,则会调用_dispatch_semaphore_wait_slow
办法。
_dispatch_semaphore_wait_slow
办法源码如下:
_dispatch_semaphore_wait_slow
- 这儿对
dispatch_time_t timeout
进行判别处理,咱们前面的比如里边传的是DISPATCH_TIME_FOREVER
,那么会调用_dispatch_sema4_wait
办法
void
_dispatch_sema4_wait(_dispatch_sema4_t *sema)
{
kern_return_t kr;
do {
kr = semaphore_wait(*sema);
} while (kr == KERN_ABORTED);
DISPATCH_SEMAPHORE_VERIFY_KR(kr);
}
_dispatch_sema4_wait
办法里边是一个do-while
循环,当不满足条件时,会一直循环下去,从而导致流程的堵塞。这也就解释了上面举例案里边的履行成果。
上面举例里边就相当于,下图中的状况
在上图框框的地方,① 相当于②,这儿是do-while
循环,所以会履行使命 2
,使命 1
一直在循环等候。
三、 总结
3.1 栅门函数
- 运用栅门函数的时分,要和其他需求履行的使命必须在同一个行列中
- 运用栅门函数不能运用大局并发行列
- 除了咱们运用,体系也在运用。
- 假如添加了栅门函数,会导致行列运转的堵塞,影响体系级的运转
3.2 信号量
-
dispatch_semaphore_wait
信号量等候,内部是对并发数做自减操作,假如为 小于0
,会履行_dispatch_semaphore_wait_slow
然后调用_dispatch_sema4_wait
是一个do-while
,知道满足条件完毕循环 -
dispatch_semaphore_signal
信号量开释 ,内部是对并发数做自加操作,直到大于0
时,为可操作 - 坚持
线程同步
,将异步履行
使命转换为同步履行
使命 - 确保
线程安全
,为线程加锁
,相当于互斥锁
专题系列文章
1.前知识
- 01-探求iOS底层原理|综述
- 02-探求iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探求iOS底层原理|LLDB
- 04-探求iOS底层原理|ARM64汇编
2. 基于OC言语探索iOS底层原理
- 05-探求iOS底层原理|OC的实质
- 06-探求iOS底层原理|OC目标的实质
- 07-探求iOS底层原理|几种OC目标【实例目标、类目标、元类】、目标的isa指针、superclass、目标的办法调用、Class的底层实质
- 08-探求iOS底层原理|Category底层结构、App启动时Class与Category装载进程、load 和 initialize 履行、相关目标
- 09-探求iOS底层原理|KVO
- 10-探求iOS底层原理|KVC
- 11-探求iOS底层原理|探索Block的实质|【Block的数据类型(实质)与内存布局、变量捕获、Block的品种、内存管理、Block的修饰符、循环引证】
- 12-探求iOS底层原理|Runtime1【isa详解、class的结构、办法缓存cache_t】
- 13-探求iOS底层原理|Runtime2【音讯处理(发送、转发)&&动态办法解析、super的实质】
- 14-探求iOS底层原理|Runtime3【Runtime的相关使用】
- 15-探求iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探求iOS底层原理|RunLoop的使用
- 17-探求iOS底层原理|多线程技能的底层原理【GCD源码剖析1:主行列、串行行列&&并行行列、大局并发行列】
- 18-探求iOS底层原理|多线程技能【GCD源码剖析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探求iOS底层原理|多线程技能【GCD源码剖析2:栅门函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探求iOS底层原理|多线程技能【GCD源码剖析3:线程调度组dispatch_group、事情源dispatch Source】
- 21-探求iOS底层原理|多线程技能【线程锁:自旋锁、互斥锁、递归锁】
- 22-探求iOS底层原理|多线程技能【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探求iOS底层原理|内存管理【Mach-O文件、Tagged Pointer、目标的内存管理、copy、引证计数、weak指针、autorelease
3. 基于Swift言语探索iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、属性
、办法
、swift多态原理
、String
、Array
、Dictionary
、引证计数
、MetaData
等Swift基本语法和相关的底层原理文章有如下几篇:
- Swift5中心语法1-基础语法
- Swift5中心语法2-面向目标语法1
- Swift5中心语法2-面向目标语法2
- Swift5常用中心语法3-其它常用语法
- Swift5使用实践常用技能点
其它底层原理专题
1.底层原理相关专题
- 01-计算机原理|计算机图形烘托原理这篇文章
- 02-计算机原理|移动终端屏幕成像与卡顿
2.iOS相关专题
- 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源耗费导致卡顿的原因和解决计划
3.webApp相关专题
- 01-Web和类RN大前端的烘托原理
4.跨渠道开发计划相关专题
- 01-Flutter页面烘托原理
5.阶段性总结:Native、WebApp、跨渠道开发三种计划性能比较
- 01-Native、WebApp、跨渠道开发三种计划性能比较
6.Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
-
02-HarmonyOS页面烘托原理 (
待输出
)
7.小程序页面烘托专题
- 01-小程序结构烘托原理