iOS 底层探索篇 —— GCD函数和队列

这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

1. 队列和线程

一个线程中可以有多个队列,每个队列中可以执行多个任务,队列可以对任务进行排序,队列依靠线程来执行任务。

1.1 主队列

主队列(Main queue)是与主线程关联的调度队列,是一种串行队列(Serial)并且在main函数之前就已经被创建了,与UI相关的操作必须放在Main queue中执行。用dispatch_get_main_queue()可以获取到主队列。
在main函数之前打下断点,看到已经有了主队列,并且是串行的。那么意味着主队列可能是在dyld之后,main函数之前创建的。

在这里插入图片描述

接下来去libdispatch源码里面查找主队列是否有初始化。

![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9a49cbfc663c48f9b6108ac572ac0b4c~tplv-k3u1fbpfcp-zoom-1.image)
    
复制代码

接下来搜索DISPATCH_GLOBAL_OBJECT。看到这里第一个是类型,而第二个才是真正的对象。那么也说明了之前传的参数dispatch_queue_main_t是类型,_dispatch_main_q才是对象

在这里插入图片描述

接下来搜索_dispatch_main_q,看到其在这里赋值了。

在这里插入图片描述

除了这种方式还可以通过搜索线程的label来寻找。这里打个断点bt一下,得到主线程的label,然后搜索。

在这里插入图片描述

直接就搜索到了主队列。这里.dq_serialnum = 1不是决定队列是不是串行队列的地方,这里的DQF_WIDTH(1)才是证明了主队列是串行队列的地方。

在这里插入图片描述

一般可以通过dispatch_queue_create来创建队列,那么在源码中搜索dispatch_queue_create这个函数的实现。

在这里插入图片描述

在这里插入图片描述

接下来搜索_dispatch_lane_create_with_target。看到这里有一百多行代码,就来看返回值。这里返回值有一个重要的东西就是dq,看到dq是dispatch_lane_t类型,并且做了开辟内存以及初始化。并且这里第三个参数做了判断,如果是concurrent则传DISPATCH_QUEUE_WIDTH_MAX,否则就传1进去。

在这里插入图片描述

接下来搜索_dispatch_queue_init函数,这里看到第三个参数用在了dqf |= DQF_WIDTH(width)这里面。这里也就证明了DQF_WIDTH(width)是确定一个队列是并行还是串行队列的地方。

在这里插入图片描述

那么dq_serialnum是代表什么呢?
搜索_dispatch_queue_serial_numbers,这里看到其赋值的地方。

在这里插入图片描述

接下来就搜索DISPATCH_QUEUE_SERIAL_NUMBER_INIT。看到了这样的注释。所以.dq_serialnum = 1代表的是主队列。

在这里插入图片描述

回到_dispatch_lane_create_with_target往上看,看到这里是在初始化参数,然后初始化队列

在这里插入图片描述

在这里插入图片描述

1.2 Global queue (全局队列)

Global queue (全局队列)运行在后台线程,是系统内共享的全局队列,是一种并行队列(Concurrent)
可以通过 dispatch_get_global_queue(0, 0)获取全局队列。
获取到Global queue 的label,然后到源码中搜索。

在这里插入图片描述

在源码中搜索到的Global queue。

在这里插入图片描述

源码往上看,看到其还是一个数组

在这里插入图片描述

1.3 队列继承链

无论是主队列,全局队列还是自定义队列,都是用dispatch_queue_t来接的。

在这里插入图片描述

点进去查看dispatch_queue_t的定义。

在这里插入图片描述

点进去查看DISPATCH_DECL的定义,发现是OS_OBJECT_DECL_SUBCLASS的封装。

在这里插入图片描述

接下来在源码中搜索OS_OBJECT_DECL_SUBCLASS,发现其定义。

在这里插入图片描述

接下来搜索OS_OBJECT_DECL_IMPL,发现是类型的拼接。

在这里插入图片描述

然后搜索OS_OBJECT_DECL_PROTOCOL,看到其定义。

在这里插入图片描述

往下搜索OS_OBJECT_CLASS,发现是OS名字的拼接。

在这里插入图片描述

那么也就是说DISPATCH_DECL(dispatch_queue);进来之后,变成了OS_dispatch_queue,
然后在OS_OBJECT_DECL_PROTOCOL中根据OS_OBJECT_DECL_IMPL中传来的参数,

@protocol OS_OBJECT_CLASS(name) __VA_ARGS__
变成:@protocol OS_OBJECT_CLASS(OS_dispatch_queue) dispatch_object

typedef adhere<OS_OBJECT_CLASS(name)>
变成:typedef NSObject<OS_dispatch_queue>

OS_OBJC_INDEPENDENT_CLASS name##_t
变成:OS_OBJC_INDEPENDENT_CLASS OS_dispatch_queue_t

搜索DISPATCH_DECL的定义,发现其还有这个定义。

在这里插入图片描述

typedef struct dispatch_queue_s : public dispatch_object_s {} *dispatch_queue_t
这也就是说,dispatch_queue_t是dispatch_queue_s类型,而dispatch_queue_s继承自dispatch_object_s。

看到这里面有个union,dispatch_object_t可以代表里面所有的类型,那么也就是说dispatch_object_t才是真正的根类型

在这里插入图片描述

这里再搜索dispatch_queue_s结构体,看到这里有个DISPATCH_QUEUE_CLASS_HEADER,是继承来的。

在这里插入图片描述

接下来搜索DISPATCH_QUEUE_CLASS_HEADER,发现也有一个继承。

在这里插入图片描述

接下来搜索DISPATCH_OBJECT_HEADER,看到其来自于_os_object_s

在这里插入图片描述

接下来搜索OS_OBJECT_STRUCT_HEADER

在这里插入图片描述

最后搜索_OS_OBJECT_HEADER

在这里插入图片描述

那么最后得到GCD的继承链:
dispatch_queue_t -> dispatch_queue_s ->dispatch_object_s-> _os_object_s -> dispatch_object_t,其最终根类为dispatch_object_t

2 GCD的任务执行堆栈

无论是异步还是同步,都有block,那么block是在哪里执行的呢?

2.1 同步队列执行堆栈

正常的调用一个同步这样的。

在这里插入图片描述

从函数的调用可以知道,dispatch_sync第一个参数是dispatch_queue_t类型,那么在源码中就可以搜索dispatch_sync(dispatch_queue_t 来进行快速搜索

在这里插入图片描述

这里的work就是传进来的block,所以需要去看一下work。
搜索一下dispatch_Block_invoke,看到这里进行了封装,接下来看这个包装函数在哪里调用执行。

在这里插入图片描述

接下来搜索_dispatch_sync_f

在这里插入图片描述

然后在搜索_dispatch_sync_f_inline,这里不知道走的哪个函数,就在函数执行的地方下断点。

在这里插入图片描述

下一个_dispatch_sync_f_slow断点后运行。

在这里插入图片描述

发现进来了,那么就代表着调用了_dispatch_sync_f_slow这个函数。

在这里插入图片描述

接下来看_dispatch_sync_f_slow的实现,看到这里有对func进行赋值操作。 还有就是_dispatch_sync_function_invoke _dispatch_sync_invoke_and_complete_recurse

在这里插入图片描述

_dispatch_sync_function_invoke_dispatch_sync_invoke_and_complete_recurse 打下断点,看看哪个执行。打下断点后运行,发现_dispatch_sync_function_invoke有被调用,_dispatch_sync_invoke_and_complete_recurse没有。

在这里插入图片描述

接下来搜索_dispatch_sync_function_invoke

在这里插入图片描述

在搜索_dispatch_sync_function_invoke_inline

在这里插入图片描述

接下来搜索_dispatch_client_callout(void,发现这里有ctxt的调用执行。

在这里插入图片描述

这里也是一样的。

在这里插入图片描述

那么就说明,在_dispatch_client_callout就会对block进行调用

也可以在block下断点然后bt来证明。

在这里插入图片描述

2.1 异步队列执行堆栈

同样的,在底层搜索dispatch_async(dispatch_queue_t

在这里插入图片描述

接下来搜索_dispatch_continuation_init(dispatch_continuation_t,之前的探索中知道 _dispatch_Block_invoke是将work进行封装。

在这里插入图片描述

接下来搜索_dispatch_continuation_init_f(dispatch_continuation_t ,看到这里进行了赋值,并且_dispatch_continuation_priority_set进行了优先级的设定。

在这里插入图片描述

那么也就是说,这个分支做了任务的封装以及优先级的封装。为什么要做封装呢?因为这里是异步函数,说明会异步调用,可能产生无序的情况,那么优先级就是参考的依据。而异步函数代表着任务回调也是异步的,并且是根据cpu的调度情况进行异步。这里对任务进行封装,那么只要cpu说可以执行了,就可以将其拿出来进行调用。

接下来搜索_dispatch_continuation_async(dispatch

在这里插入图片描述

然后搜索dx_push
这里z是qos,我们只关注z,所以搜索dq_push

在这里插入图片描述

接下来搜索dq_push,发现其在全局队列时候的赋值。

在这里插入图片描述

接下来搜索_dispatch_root_queue_push,发现最后都会调用_dispatch_root_queue_push_inline

在这里插入图片描述

搜素一下_dispatch_root_queue_push_inline

在这里插入图片描述

然后搜索_dispatch_root_queue_poke

在这里插入图片描述

再搜索_dispatch_root_queue_poke_slow

在这里插入图片描述

接下来看_dispatch_root_queues_init

在这里插入图片描述

这里调用了_dispatch_root_queues_init_once,看到这里有个_dispatch_worker_thread2

在这里插入图片描述

回到程序里面bt进行逆向推导,发现这里有调用_dispatch_worker_thread2

在这里插入图片描述

在这里插入图片描述

然后是_dispatch_root_queue_drain,看到这里的_dispatch_continuation_pop_inline

在这里插入图片描述

搜索_dispatch_continuation_pop_inline

在这里插入图片描述

接下来走到_dispatch_continuation_invoke_inline

在这里插入图片描述

然后调用了_dispatch_client_callout

在这里插入图片描述

而在_dispatch_client_callout就调用了block

在这里插入图片描述

3. 面试题

这里的函数会输出什么呢?这里的答案是A:1230789,因为这里是串行队列,那么任务就会按顺序执行,所以打印出来的是1230789。

在这里插入图片描述

这里的运行结果会是什么呢?答案是大于等于5。这里的while循环,确保了num至少为5,而因为是异步执行self.num++,那么可能在self.num >= 5 的时候,还有线程在运行self.num++,那么如果其在NSLOG之前完成了,那么self.num就大于等于5了。也有一种可能是例如在self.num 等于3时,恰好有多个线程完成了self.num++,那么self.num就会大于等于5,退出循环了。

在这里插入图片描述

那么这里的运行结果是多少呢?是不是>= 10000呢?答案是小于等于10000,因为这里的循环判定条件是i,当 i=10000 退出循环时,下面有的线程可能还没执行完,那么就会小于等于10000了。同时这里线程也不安全,当上一个线程还未进行++操作,值为100时,那么下一个线程读的时候,也是100了,而这个时候上一个线程进行++操作,下一个也进行了++操作,结果都是101。而不是102。

在这里插入图片描述

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享