dispatch_once
概述
dispatch_once
能保证任务只会被执行一次,即使同时多线程调用也是线程安全的。常用于创建单例、swizzeld method等功能。
使用
1 | static dispatch_once_t onceToken; |
源码分析
1 | //调用dispatch_once_f来处理 |
封装dispatch_once_f
函数,通过_dispatch_Block_invoke
来执行block,它的定义如下:
1 | //invoke是指触发block的具体实现,感兴趣的可以看一下Block_layout的结构体 |
dispatch_once_f
函数实现:
1 | void dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func) { |
1 | //构造的链表 |
分三种情况进行分析:
- 只有一个线程调用:此时传入的onceToken为空指针,if判断成立。通过
_dispatch_client_callout
执行block,然后将vval的值设为DISPATCH_ONCE_DONE
,表示任务已完成,同时调用tmp保存先前的vval。此时,dow也为空,while判断不成立,代码执行结束。 - 同一线程第二次调用,此时vval已经变为了
DISPATCH_ONCE_DONE
,因此 if 判断不成立,进入 else 分支的 for 循环。由于 tmp 就是DISPATCH_ONCE_DONE,所以循环退出,没有做任何事。 - 多个线程同时调用,由于 if 判断中是一个原子性操作,所以必然只有一个线程能进入 if 分支,其他的进入 else 分支。由于其他线程在调用函数时,vval 还不是
DISPATCH_ONCE_DONE
, 进入到for循环后半部分,这里构造了一个链表,链表的每个节点上都调用了信号量的 wait 方法并阻塞,而在 if 分支中,则会依次遍历所有的节点并调用 signal 方法,唤醒所有等待中的信号量。
总结
dispatch_once
用原子性操作block执行完成标记位,同时用信号量确保只有一个线程执行block,等block执行完再唤醒所有等待中的线程。