• 1

  • 474

  • 收藏

iOS底层 - 多线程之底层篇

3星期前

欢迎阅读iOS底层系列(建议按顺序)

iOS底层 - alloc和init探索

iOS底层 - 包罗万象的isa

iOS底层 - 类的本质分析

iOS底层 - cache_t流程分析

iOS底层 - 方法查找流程分析

iOS底层 - 消息转发流程分析

iOS底层 - dyld是如何加载app的

iOS底层 - 类的加载分析

iOS底层 - 分类的加载分析

iOS探索 - 多线程之相关原理

iOS探索 - 多线程之GCD应用

1.写在前面

本文旨在通过分析GCD相关的底层实现,充分掌握上层API的使用技巧。

2.初步分析

通常使用越是方便的API,往往越不会去窥探它的原理,容易形成拿来即用的习惯,而GCD就像是这样的存在。

那么分析GCD的底层源码的思路是什么?应该从何看起?

———————————————————————————————————————————————————

还记得GCD的核心思想嘛:

将任务添加到队列,并且指定执行任务的函数

分析GCD的底层源码就是紧紧围绕这个思想展开:

  • 队列是怎么被创建的
  • 同步异步函数的区别
  • 任务是何时被执行的

3.底层初探

研究源码,首先需要找到源码所在的库。

GCD的底层相对Objc来说是晦涩难懂的,因为它的分支较多,宏定义较多,命名较长,系统给出的注释较少,且涉及大量内核级的操作,所以分析GCD不会逐行逐句的解释,只会挑选其中的关键部分。

———————————————————————————————————————————————————

底层代码都来自苹果的开源库

苹果的opensource之libdispatch

我的github有部分注解,后续会陆续更新

底层注解github之libdispatch

3.1 队列的创建

探索的重点在于:

  • 队列是怎么被创建的
  • 串行和异步是怎么被区分的

————————————————————————————————————————————————————————————————————————————

队列的创建是使用dispatch_queue_create是我们知道的,那么可以在项目中下个dispatch_queue_create符号断点。

运行后显示:

从中可知,dispatch_queue_create是在libdispatch.dylib库中,并且马上调用_dispatch_lane_create_with_target

苹果底层函数常使用"_"前缀,起到上层函数改变,底层也能快速适配的思想。
复制代码

libdispatch中尝试搜索_dispatch_lane_create_with_target,

只有三个结果,并且第一个就是函数实现。

  • dqai的初始化
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
复制代码

在创建队列时,我们指定的队列类型就是dqa(串行或并发),传入的dqa通过_dispatch_queue_attr_to_info函数得到类型为dispatch_queue_attr_info_tdqai

dispatch_queue_attr_info_t采用位域的存储方式,其结构体如下:

typedef struct dispatch_queue_attr_info_s {
	dispatch_qos_t dqai_qos : 8;
	int      dqai_relpri : 8;
	uint16_t dqai_overcommit:2;
	uint16_t dqai_autorelease_frequency:2;
	uint16_t dqai_concurrent:1;
	uint16_t dqai_inactive:1;
} dispatch_queue_attr_info_t;
复制代码

,而_dispatch_queue_attr_to_info的函数实现如下

1.1 初始化了空的结构体,如果dqa是为空,则直接返回空的结构体。

这句代码的意思:当为串行队列的(串行传NULL)时候,dqai为空结构体,并返回。

1.2 当dqa不为空时,对结构体内的位域赋值,最后返回dqai。

这句代码的意思:当为并发队列的(并发为非NULL)时候,对结构体内的位域赋值,并返回。
复制代码

串行和并发的区别在第一行代码就被区分,返回的dqai的状态就是两者后续判断的分水岭。

  • overcommit的赋值

overcommit是用于后续计算模板数组下标的值。

overcommit = dqai.dqai_concurrent ?
        _dispatch_queue_attr_overcommit_disabled :
        _dispatch_queue_attr_overcommit_enabled;
复制代码

根据dqai.dqai_concurrent是否存在,决定overcommit的值。串行队列的dqai为空,所以

队列类型 overcommit
串行 _dispatch_queue_attr_overcommit_enabled
并发 _dispatch_queue_attr_overcommit_disabled
  • ③ 初始化tq(要执行block块的目标队列)
tq = _dispatch_get_root_queue(
        qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
        overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
复制代码

其中

计算方式:

qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
复制代码

相关的宏定义:

#define DISPATCH_QOS_UNSPECIFIED        ((dispatch_qos_t)0)
#define DISPATCH_QOS_DEFAULT            ((dispatch_qos_t)4)
typedef enum {
	_dispatch_queue_attr_overcommit_unspecified = 0,
	_dispatch_queue_attr_overcommit_enabled,
	_dispatch_queue_attr_overcommit_disabled,
} _dispatch_queue_attr_overcommit_t;
复制代码

qosDISPATCH_QOS_UNSPECIFIED未指明时,设置为默认DISPATCH_QOS_DEFAULT,否则为设定的qos,因为创建队列时,我们一般不会指定优先级,所以qosDISPATCH_QOS_DEFAULT,等于4

overcommit上一步赋值过,是个枚举值,串行为1(_dispatch_queue_attr_overcommit_enabled),并发为2(_dispatch_queue_attr_overcommit_disabled)

然后把值传入_dispatch_get_root_queue函数进行计算

我们可以很快速的算出2 * (qos - 1) + overcommit的值:

队列类型 2 * (qos - 1) + overcommit
串行 7
并发 8

最终的值已经得到,先做个标记,后面在回头分析这个值的意义。

  • ④ 设置vtable

并发为queue_concurrent,串行为queue_serialvtable是后续初始化队列时用来拼接字符串的标识符。

#define DISPATCH_CLASS_SYMBOL(name) _dispatch_##name##_vtable
复制代码

GCD中很多宏定义使用##参数##,这个意思其实就是字符串拼接。

  • ⑤ 分配空间并初始化队列

1.1 先用_dispatch_object_alloc根据串行还是并发使用vtablealloc出对应类的空间

搜索底层代码有个小技巧,直接搜索_dispatch_object_alloc关联较多,可以根据它的参数类型进行搜索优化。因为第一个参数是const void *类型,所以可以直接搜_dispatch_object_alloc(const void *缩小范围。
复制代码

_dispatch_object_alloc函数一路跟下去会来到

static inline id _os_objc_alloc(Class cls, size_t size){
      id obj;
      size -= sizeof(((struct _os_object_s *)NULL)->os_obj_isa);
      while (unlikely(!(obj = class_createInstance(cls, size)))) {
        _dispatch_temporary_resource_shortage();
      }
      return obj;
}
复制代码

可以看到,vtable拼接之后最终就是转成对应的cls

objc的源码中,我们知道存在objc_object,这里出现了dispatch_object又是什么?

搜索dispatch_object

看到dispatch_object_t类型,继续搜索dispatch_object_t

typedef union {
	struct _os_object_s *_os_obj;
	struct dispatch_object_s *_do;
	struct dispatch_queue_s *_dq;
	struct dispatch_queue_attr_s *_dqa;
	struct dispatch_group_s *_dg;
	struct dispatch_source_s *_ds;
	struct dispatch_mach_s *_dm;
	struct dispatch_mach_msg_s *_dmsg;
	struct dispatch_semaphore_s *_dsema;
	struct dispatch_data_s *_ddata;
	struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
复制代码

发现dispatch_object_t是个联合体,联合体的形式在isa中也有出现过。

里面包括很多常见的GCD类型,比如dispatch_group_sdispatch_source_sdispatch_semaphore_sdispatch_queue_s,他们都是dispatch_object_t类型。

typedef struct dispatch_object_s {
private:
	dispatch_object_s();
	~dispatch_object_s();
	dispatch_object_s(const dispatch_object_s &);
	void operator=(const dispatch_object_s &);
} *dispatch_object_t;
复制代码

并且采用*dispatch_object_t又包装了dispatch_object_s,层层传递。

这种设计模式类似声明一个基类,然后层层继承下去,最底层都是这个基类。不过使用基类会导致后面类越来越庞大,难以维护,联合体的形式避免了这个缺点。

1.2 使用_dispatch_queue_init构造函数,初始化dq,第三个参数为width,并且有以下宏定义:

#define DISPATCH_QUEUE_WIDTH_FULL			0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
复制代码
队列类型 width
串行 1
并发 DISPATCH_QUEUE_WIDTH_MAX

width代表能同时执行任务的最大数,DISPATCH_QUEUE_WIDTH_POOL后续说明。

1.3 设置队列dq的属性目标队列targetqtq

dq->do_targetq = tq;
复制代码

由此可知,dqtq不是同一个东西,dq是最终初始化的对象,tq是执行block代码的队列。tqdq的属性之一。


至此,队列的属性被赋值,目标队列被设置,初始化完成。

欣喜之余,思考下:

tq是怎么分配的,每次都需要初始化嘛?

关于tq的创建,上面的分析并没有提到。我们先回到最初的起点看看线程是怎么创建的?

dispatch_queue_t queue1 = dispatch_queue_create("juejin", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_queue_t queue2 = dispatch_queue_create("juejin", NULL);
复制代码

分别打印两个队列:

队列类型 width target
串行 0x1 com.apple.root.default-qos
并发 0xffe com.apple.root.default-qos.overcommit

关于width_dispatch_queue_init构造函数已经说明:

自己创建的串行队列为0x1,自己创建的并发队列为0xffe,还有一个宏定义为DISPATCH_QUEUE_WIDTH_POOL(0xfff)

搜索下DISPATCH_QUEUE_WIDTH_POOL

static const struct dispatch_queue_global_s _dispatch_custom_workloop_root_queue = {
	DISPATCH_GLOBAL_OBJECT_HEADER(queue_global),
	.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE,
	.do_ctxt = NULL,
	.dq_label = "com.apple.root.workloop-custom",
	.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
	.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
			DISPATCH_PRIORITY_SATURATED_OVERRIDE,
	.dq_serialnum = DISPATCH_QUEUE_SERIAL_NUMBER_WLF,
	.dgq_thread_pool_size = 1,
};
复制代码

DISPATCH_QUEUE_WIDTH_POOL是全局队列使用的。

关于targetdqtargettq,那tq究竟是怎么创建的?

回到上面标记的2 * (qos - 1) + overcommit的值,

tq = _dispatch_get_root_queue(
        qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
        overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
复制代码

_dispatch_get_root_queue内部通过数组模版获取对应的tq

&_dispatch_root_queues[2 * (qos - 1) + overcommit];

查看下_dispatch_root_queues[]的定义:

struct dispatch_queue_global_s _dispatch_root_queues[] = {
#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
		((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
		DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
		DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
	[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
		DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
		.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
		.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
		.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
		.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
				_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
				_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
		__VA_ARGS__ \
	}
	_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
		.dq_label = "com.apple.root.maintenance-qos",
		.dq_serialnum = 4,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.maintenance-qos.overcommit",
		.dq_serialnum = 5,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
		.dq_label = "com.apple.root.background-qos",
		.dq_serialnum = 6,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.background-qos.overcommit",
		.dq_serialnum = 7,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
		.dq_label = "com.apple.root.utility-qos",
		.dq_serialnum = 8,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.utility-qos.overcommit",
		.dq_serialnum = 9,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
		.dq_label = "com.apple.root.default-qos",
		.dq_serialnum = 10,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
			DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.default-qos.overcommit",
		.dq_serialnum = 11,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
		.dq_label = "com.apple.root.user-initiated-qos",
		.dq_serialnum = 12,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-initiated-qos.overcommit",
		.dq_serialnum = 13,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
		.dq_label = "com.apple.root.user-interactive-qos",
		.dq_serialnum = 14,
	),
	_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
		.dq_label = "com.apple.root.user-interactive-qos.overcommit",
		.dq_serialnum = 15,
	),
};

复制代码

其内部声明了多种模版,而串行和并发通过计算得到的下标值的78,数下来正好对应com.apple.root.default-qoscom.apple.root.default-qos.overcommit,和打印的值不谋而合。

自定义串行和并发的下标取值因为overcommit的不同而不同。

所以,tq是根据串行和并发的值通过下标去模版数组直接获取的,不需要每次都去创建,提高性能。

那么,模版数组又是什么时候创建的?

可以想像,模版数组一定是伴随着libdispatch初始化时初始化的。

① 初始化模版数组

for循环中的DISPATCH_ROOT_QUEUE_COUNT是个多层嵌套的宏定义:

#define DISPATCH_ROOT_QUEUE_COUNT   (DISPATCH_QOS_NBUCKETS * 2)
#define DISPATCH_QOS_NBUCKETS       (DISPATCH_QOS_MAX - DISPATCH_QOS_MIN + 1)
#define DISPATCH_QOS_MIN            DISPATCH_QOS_MAINTENANCE
#define DISPATCH_QOS_MAX            DISPATCH_QOS_USER_INTERACTIVE
复制代码

一顿宏定义后,DISPATCH_ROOT_QUEUE_COUNT为12。而_dispatch_root_queues[]内的模版的dq_serialnum是4-15(缺少123),刚好存在12个模板。

② 初始化主队列和全局队列

搜索_dispatch_main_q_dispatch_mgr_q

_dispatch_main_q是主队列。_dispatch_mgr_qdq_labelcom.apple.root.libdispatch-manager的队列,它的do_targetq指向_dispatch_mgr_root_queue,也就是全局队列。三者的dq_serialnum分别为123(补其模板数组最小值4之前的空缺)。

主队列全局队列libdispatch初始化时也被初始化,这也是为什么能在全局直接使用它们的原因。

看了看dispatch_get_main_queue的实现,_dispatch_main_q一目了然。

队列创建的总结:

  • 自定义队列传的参数决定了dqaidqai决定了队列是串行还是并发
  • 自定义队列的目标队列(tq)是使用模版数组快速创建的
  • 全局队列,主队列,模版数组是伴随者libdispatch初始化的

3.2 任务执行

探索的重点在于:

  • block都需要手动调用,而GCD的任务没有显示调用,为什么会被执行

——————————————————————————————————————

在任务内部打上断点查看堆栈信息,

堆栈内_dispatch_client_callout异常明显

static inline void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
	return f(ctxt);
}
复制代码

GCD把任务进行包装成dispatch_function_t类型的参数f,在合适的时候系统会调用_dispatch_client_callout执行任务,而不需要外界显示调用。

3.3 同步函数

探索的重点在于:

  • 同步函数为什么会顺序执行任务
  • 同步函数使用过程可能出现的问题,比如死锁是怎么产生的

——————————————————————————————————————

同步dispatch_sync是我们熟悉的。直接搜索,不停跳转

dispatch_sync -> _dispatch_sync_f -> _dispatch_sync_f_inline

参数func需要注意下:任务被包装成dispatch_function_t类型,在合适的地方调用。

① 先判断dq_width是否等于1,若相等就是串行队列,走_dispatch_barrier_sync_f,方法名有很熟悉的栅栏函数,这也从侧面说明了栅栏函数同步串行是很类似的。

顺便回忆下死锁发生时的堆栈信息,

串行队列探索

居然发现,_dispatch_sync_f_slow赫然在列,似乎是那个点了。

_dispatch_barrier_sync_f_inline方法内部先通过_dispatch_tid_self获取线程ID,然后_dispatch_queue_try_acquire_barrier_sync判断线程是否在等待。如果等待进入_dispatch_sync_f_slow,否则调用_dispatch_lane_barrier_sync_invoke_and_complete最终也来到_dispatch_client_callout执行任务。

此时来到_dispatch_sync_f_slow

方法内部先调用_dispatch_trace_item_push将任务用push的形式入队,队列因为是FIFO,所以实现了顺序的结构。

然后来到__DISPATCH_WAIT_FOR_QUEUE__

通过调用_dispatch_wait_prepareos底层获取队列任务状态,然后把刚才获取的任务的状态和此任务状态传入,(一个是执行同步函数自身的任务,一个是执行同步函数内block的任务)

_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
	return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
复制代码

两者进行异或运算,如果相同,即都为等待,则系统抛出死锁

若不相同,则只是单方面等待,调用_dispatch_sync_invoke_and_complete_recurse递归执行任务。需要递归的原因是,任务的调度是系统底层行为,不知道等待何时结束。

GCD底层有大量调用os底层来获取状态回调的,是因为任务是否执行是依赖线程的状态是否良好,而线程的调度是系统底层行为

并发队列探索

dq_width不等于1的情况,也就是并发队列,直接调用_dispatch_sync_invoke_and_complete执行任务。

内部调用_dispatch_sync_function_invoke_inline,然后来一个任务_dispatch_thread_frame_push一下,_dispatch_client_callout执行,执行完_dispatch_thread_frame_pop一下。同样实现了顺序执行的结构。

总结:

  • 同步函数的任务采用了push入队,pop出队的方式达到了顺序执行
  • 同步函数当产生互相等待时,会发生死锁
  • 同步函数和栅栏函数是类似的

3.4 异步函数

探索的重点在于:

  • 异步函数是怎么创建线程的
  • 异步函数的任务是何时被包装的

——————————————————————————————————————

还是一样的思路,直接搜索dispatch_async(dis

void
dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
{
      //获取当前线程中绑定的 queue.
      dispatch_continuation_t dc = _dispatch_continuation_alloc();
      uintptr_t dc_flags = DC_FLAG_CONSUME;
      dispatch_qos_t qos;

      //包装任务并返回对应的优先级
      qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
      _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
复制代码
  • 先看_dispatch_continuation_init, 跳转到_dispatch_continuation_init_f

函数内部把任务及其相关属性包装起来。

  • 在看_dispatch_continuation_async

dx_push是个宏定义调用dq_push

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
复制代码

dq_push有很多赋值,

直接探索_dispatch_root_queue_push,因为其他的最终也会来到_dispatch_root_queue_push,只关注想要探索的点,不去关心太多的旁跟末节。

的参数区别在于qos,看到这里,我们可以用反推法:

已知异步串行是顺序执行任务,异步并发是乱序执行任务。qos会影响任务调度的优先级。

所以可得:①是并发的分支,②是串行的分支。
复制代码

虽然这里有分支,但是看下并发的分支,

简单的处理之后,就回到了串行分支的_dispatch_root_queue_push_inline,所以无论串行还是并发,最终都是来到_dispatch_root_queue_push_inline。并发的处理步骤多于串行也是可以理解的。

跳转:_dispatch_root_queue_push_inline -> _dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow

can_request:能请求的线程数
t_count:线程池大小
remaining:剩下的
复制代码

方法内部使用了两层do-whild循坏。两层循环动态的维护线程池的容量。

第一层是用来判断和控制线程池状态的,每次请求到线程remaining = can_request,线程池满了就不做处理;

第二层使用pthread_create来创建线程,请求到了--remaining,使剩下的线程总数减1。

总结:

  • 异步函数使用pthread_create创建线程
  • 异步函数的任务在线程创建之前就被包装,等待被线程调度

3.5 单例

跳转:dispatch_once -> dispatch_once_f

① 传入的参数onceToken,被转换成dispatch_once_gate_t类型的变量ll是底层原子操作的参数,这里通过os_atomic_load获取状态值v

② 如果状态值为DLOCK_ONCE_DONE,说明任务已经执行过了,直接返回。

③ 如果此时任务没有执行过,则会在底层通过原子操作,将任务进行加锁,目的是为了保证当前任务执行的唯一性。加锁之后进行回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE

④ 如果在任务执行期间,其他任务进来会调用_dispatch_once_wait进入无限次等待,原因是当前任务已经获取了锁,其他任务是无法获取锁的。

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_once_callout内部调用_dispatch_client_callout执行任务,然后调用_dispatch_once_gate_broadcast进行广播修改任务状态值。

3.6 信号量

信号量三部曲:dispatch_semaphore_createdispatch_semaphore_waitdispatch_semaphore_signal

dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
	dispatch_semaphore_t dsema;
	if (value < 0) {
		return DISPATCH_BAD_INPUT;
	}
_dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
			sizeof(struct dispatch_semaphore_s));
	dsema->do_next = DISPATCH_OBJECT_LISTLESS;
	dsema->do_targetq = _dispatch_get_default_queue(false);
	dsema->dsema_value = value;
	_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
	dsema->dsema_orig = value;
	return dsema;
}
复制代码

根据传入的参数输出dispatch_semaphore_t类型且值为value的信号量。需要注意的是,信号量的起始值传递小于0的值将导致返回NULL

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
	long value = os_atomic_dec2o(dsema, dsema_value, acquire);
	if (likely(value >= 0)) {
		return 0;
	}
	return _dispatch_semaphore_wait_slow(dsema, timeout);
}
复制代码

内部调用os_atomic_dec2o使传入的信号量dsema的值减1。os_atomic_dec2o底层被定义的符号为"-"

_os_atomic_c11_op((p), (v), m, sub, -)
复制代码

如果value的值大于等于0(说明信号量大于等于1),该函数所处线程就继续执行下面的语句;否则,这个函数就阻塞当前线程。

如果等待的期间信号量的值被dispatch_semaphore_signal函数加1了,且是该函数所处线程获得了信号量,就继续向下执行并将信号量减1。

如果一直没有获取信号量,那么等到超时时间到来时,其所处线程也会自动执行后续语句。

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema){
	long value = os_atomic_inc2o(dsema, dsema_value, release);
	if (likely(value > 0)) {
		return 0;
	}
	if (unlikely(value == LONG_MIN)) {
		DISPATCH_CLIENT_CRASH(value,
				"Unbalanced call to dispatch_semaphore_signal()");
	}
	return _dispatch_semaphore_signal_slow(dsema);
}
复制代码

内部调用os_atomic_inc2o使信号量加1。os_atomic_inc2o底层被定义的符号为"+"

_os_atomic_c11_op_orig((p), (v), m, add, +)
复制代码

value小于等于0时(说明信号量为负)表示当前并没有线程需要处理其后续语句。

value大于0时,表示有线程(线程数等于value值)需要处理其后续语句,并且该函数唤醒了一个等待的线程,如果线程存在优先级,唤醒优先级最高的线程,否则随机唤醒。

3.7 调度组

dispatch_group_create:

dispatch_group_t
dispatch_group_create(void){
	return _dispatch_group_create_with_count(0);
}

static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n){
	dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
			sizeof(struct dispatch_group_s));
	dg->do_next = DISPATCH_OBJECT_LISTLESS;
	dg->do_targetq = _dispatch_get_default_queue(false);
	if (n) {
		os_atomic_store2o(dg, dg_bits,
				-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
		os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); 
	}
	return dg;
}
复制代码

创建调度组,正常都是0,os底层不保存value;如果参数非0,os底层保存value为1。

dispatch_group_enter

void
dispatch_group_enter(dispatch_group_t dg){
      uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
              DISPATCH_GROUP_VALUE_INTERVAL, acquire);
      uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
      ...
}
复制代码

调用os_atomic_sub_orig2o使任务计数value减1

dispatch_group_leave:

① 调用os_atomic_add_orig2o任务计数加1

_dispatch_group_wake内部调用_dispatch_continuation_async(异步函数分析过)异步执行任务

leave次数如果多过enter,程序崩溃

dispatch_group_async:

dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
		dispatch_block_t db)
{
	dispatch_continuation_t dc = _dispatch_continuation_alloc();
	uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
	dispatch_qos_t qos;

	qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
	_dispatch_continuation_group_async(dg, dq, dc, qos);
}
复制代码

和异步函数但执行过程相似,_dispatch_continuation_init保存任务,_dispatch_continuation_group_async异步执行任务

需要注意但是,

异步执行任务对于group来说也是个任务,也是需要enter的,在执行任务后,也会调用一次leave

4.写在后面

以上是对GCD底层很浅显的分析,它已经有些晦涩了,而它之下还有os底层,posix下层等..想想就头大。

不过探索源码就是个令人头秃的过程,换句话说也就是变强的过程。

--无论你从什么时候开始,重要的是开始后就不要停止。

后面陆续还会更新block内存管理等底层原理,敬请关注。

免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

ios

474

相关文章推荐

未登录头像

暂无评论