OC-多线程-单例模式
说起单例,我们一般使用GCD的dispath_once来创建单例
对于单例,需要知道以下两个问题:
- 1.单例为什么只执行一次,底层是如何控制的
- 2.单例的block是在什么时候进行调用
下面我们来探究一下
单例为什么只执行一次
再进入dispatch_once的源码前,我们先看下dispatch_once的参数
- 1.onceToken,这是一个
静态变量
,由于不同位置定义的静态变量是不同
的,所以静态变量具有唯一性
。 - 2.block回到
我们看到会调用dispatch_once_f,其中val是外界传入
的onceToken静态变量
,而func是_dispatch_Block_invoke(block),我们看下dispatch_once_f的底层实现
通过上面代码,可以知道底层主要分为以下几步
- 1.
将val,也就是静态变量转换为dispatch_once_gate_t类型变量l
- 2.通过
os_atomic_load获取此时的任务的标识符v
- 3.如果
v等于DLOCK_ONCE_DON
E,表示任务执行过
了,只接return - 4.如果
任务执行后,加锁失败
了,则走到_dispatch_once_mark_done_if_quiesced函数,函数里再次进行存储
,将标识符置为DLOCK_ONCE_DONE
。 - 5.反之,则通过
_dispatch_once_gate_tryenter尝试进入任务
,即解锁,然后执行_dispatch_once_callout执行block回调 - 6.如果此时
有任务正在执行
,再有任务进来
,则通过_dispatch_once_wait函数
让新来的任务进入无限次等待
。
单例block是什么时候调用
上面我们知道func就是任务block
,而处理func的方法就是_dispatch_once_callout
,前面判断_dispatch_once_gate_tryenter解锁
,我们看下_dispatch_once_gate_tryenter这个方法实现
其源码主要是通过底层的os_atomic_cmpxchg方法进行对比
,如果比较没有问题
,则进行加锁,即任务的标识置为DLOCK_ONCE_UNLOCKED
。 我们下面看下_dispatch_once_callout方法源码
上面方法主要分两步:
- 1._dispatch_client_callout:block回调执行
- 2._dispatch_once_gate_broadcast:进行广播
先看下_dispatch_client_callout方法实现
_dispatch_client_callout主要执行回调,其中f就是传入的_dispatch_Block_invoke(block),即异步回调
再看下_dispatch_once_gate_broadcast方法实现
进入 _dispatch_once_gate_broadcast -> _dispatch_once_mark_done源码,主要就是
给dgo->dgo_once一个值
,然后将任务的标识符为DLOCK_ONCE_DONE
,即解锁
。
单例总结
上面我们对单例进行了探索,解开了上面所提出的问题。下面总结一下:
- 1.【单例执行一次原理】:GCD单例中,有两个重要参数,
onceToken
和block
,其中onceToken是静态变量
,具有唯一性
,在底层被封装成了dispatch_once_gate_t类型的变量l
,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态
,如果此时v等于DLOCK_ONCE_DONE
,说明任务已经处理过一次
了,直接return。 - 2.【block调用时机】:如果此时
任务没有执行过
,则会在底层通过C++函数的比较
,将任务进行加锁
,即任务状态置为DLOCK_ONCE_UNLOCK
,目的是为了保证当前任务执行的唯一性
,防止在其他地方有多次定义
。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁
,将当前的任务状态置为DLOCK_ONCE_DONE
,在下次进来时,就不会在执行,会直接返回
- 3.【对多线程的印象】:如果在
当前任务执行期间,有其他任务进来,会进入无限次等待
,原因是当前任务已经获取了锁
,进行了加锁,其他任务是无法获取锁
的。