iOS底层原理(七)多线程(下)
iOS中的读写安全
atomic
atomic
用于保证属性setter、getter
的原子性操作,相当于在getter和setter
内部加了线程同步的锁原子性:原子即为最小的物理单位,意味不可再分割;即代码都为一个整体在同一线程进行操作
atomic
只是保证setter、getter
是线程安全的,并不能保证使用属性的过程是线程安全的
从源码分析getter和setter
对于atomic
的使用
我们在objc4
中的objc-accessors.mm
中找到对应的getter和setter
的实现
getter
的实现
// getter
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
setter
的实现
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
从源码可以看出只有automic
的属性才会进行加锁操作
iOS中的读写安全方案
思考以下场景,怎么做最合适
- 同一时间,只能有1个线程进行写的操作
- 同一时间,允许有多个线程进行读的操作
- 同一时间,不允许既有写的操作,又有读的操作
上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有以下两个
- pthread_rwlock:读写锁
- dispatch_barrier_async:异步栅栏调用
pthread_rwlock
pthread_rwlock
是专用于读写文件的锁,其本质也是互斥锁,等待锁的线程会进入休眠
使用代码如下
@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化锁
pthread_rwlock_init(&_lock, NULL);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
[self read];
});
dispatch_async(queue, ^{
[self write];
});
}
}
- (void)read {
// 加上读取数据的锁
pthread_rwlock_rdlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)write
{
// 加上写入数据的锁
pthread_rwlock_wrlock(&_lock);
sleep(1);
NSLog(@"%s", __func__);
pthread_rwlock_unlock(&_lock);
}
- (void)dealloc
{
// 释放时要销毁锁
pthread_rwlock_destroy(&_lock);
}
@end
dispatch_barrier_async
dispatch_barrier_async
也叫栅栏函数,意在用于拦截多线程异步并发操作,只保证同时有一条线程在操作
用栅栏函数也可以保证多读单写的操作
使用代码如下
@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
dispatch_async(self.queue, ^{
[self read];
});
// 这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的
dispatch_barrier_async(self.queue, ^{
[self write];
});
}
}
- (void)read {
sleep(1);
NSLog(@"read");
}
- (void)write
{
sleep(1);
NSLog(@"write");
}
@end
定时器
我们日常使用的定时器有以下几个
- CADisplayLink
- NSTimer
- GCD定时器
CADisplayLink
CADisplayLink
是用于同步屏幕刷新频率的定时器
CADisplayLink和NSTimer的区别
- iOS设备的屏幕刷新频率是固定的,
CADisplayLink
在正常情况下会在每次刷新结束都被调用,精确度相当高 NSTimer
的精确度就显得低了点,比如NSTimer
的触发时间到的时候,runloop
如果在阻塞状态,触发时间就会推迟到下一个runloop
周期。并且NSTimer
新增了tolerance
属性,让用户可以设置可以容忍的触发的时间的延迟范围CADisplayLink
使用场合相对专一,适合做UI的不停重绘,比如自定义动画引擎或者视频播放的渲染。NSTimer
的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。在UI相关的动画或者显示内容使用CADisplayLink
比起用NSTimer
的好处就是我们不需要在格外关心屏幕的刷新频率了,因为它本身就是跟屏幕刷新同步的。
CADisplayLink在使用中会出现的循环引用问题
CADisplayLink
在日常使用中,可能会出现循环引用问题,见示例代码
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
}
@end
由于ViewController
里有个link属性
指向这CADisplayLink对象
,CADisplayLink对象
里的target
又指向着ViewController
里的linkTest
,都是强引用,所以会造成循环引用,无法释放
解决方案
增加第三个对象,通过第三个对象将target调用的方法转发出去,具体如下图所示
实现代码如下
@interface LLProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LLProxy
+ (instancetype)proxyWithTarget:(id)target
{
LLProxy *proxy = [[LLProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.target;
}
@end
// ViewController.m文件中
#import "ViewController.h"
#import "LLProxy.h"
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:[LLProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)linkTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.link invalidate];
}
@end
NSTimer
NSTimer
也是定时器,相比CADisplayLink
使用范围更广,更灵活,但精确度会低一些
NSTimer在使用中会出现的循环引用问题
NSTimer
在使用时也会存在循环引用问题,同CADisplayLink
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
解决方案
【第一种】借助第三对象并将方法转发,同CADisplayLink
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[LLProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest
{
NSLog(@"%s", __func__);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
【第二种】使用NSTimer
的block回调
来调用方法,并将self
改为弱指针
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
统一优化方案
NSProxy
NSProxy
是唯一一个没有继承自NSObject
的类,它是专门用来做消息转发的
特点
- 不继承
NSObject
,也是基类类型 - 没有
init方法
,直接用alloc方法
来初始化 - 没有
forwardingTargetForSelector方法
,只支持消息转发
优化方案
将LLProxy
继承自NSProxy
,然后在消息转发里替换target
这么做的好处在于NSProxy
相比NSObject
少了消息发送先从父类查找的过程,以及不经过forwardingTargetForSelector
,相比之下性能会高
替换代码如下
@interface LLProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
@implementation LLProxy
+ (instancetype)proxyWithTarget:(id)target
{
// NSProxy对象不需要调用init,因为它本来就没有init方法
LLProxy *proxy = [LLProxy alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
[invocation invokeWithTarget:self.target];
}
@end
从源码实现来分析
我们先查看下面这句代码打印什么
ViewController *vc = [[ViewController alloc] init];
LLProxy *proxy = [LLProxy proxyWithTarget:vc];
NSLog(@"%d, [proxy isKindOfClass:[ViewController class]]);
打印结果为1,可以看出NSProxy
的isKindOfClass
和NSObject
的isKindOfClass
有所差别
我们可以通过GNUstep
来查看NSProxy
的源码实现,发现其内部会直接调用消息转发的方法,才会有我们将target替换成了ViewController
对象,所以最后调用isKindOfClass
的是ViewController对象
,那么结果也就知晓了
从该方法可以反观NSProxy
的其他方法内部实现,都会主动触发消息转发的实现
GCD定时器
GCD定时器
相比其他两个定时器是最准时的,因为和系统内核直接挂钩
使用代码如下
我们将GCD定时器
封装到自定义的LLTimer
文件来使用
// LLTimer.h文件
@interface LLTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
// LLTimer.m文件
#import "LLTimer.h"
@implementation LLTimer
static NSMutableDictionary *timers_;
static dispatch_semaphore_t semaphore_;
+ (void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
// 加锁来保证多线程创建定时器和取消定时器同时只能有一个操作
semaphore_ = dispatch_semaphore_create(1);
});
}
+ (NSString *)execTask:(void (^)(void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
// dispatch_time_t start:几秒后开始执行
// uint64_t interval:执行间隔
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
// 去掉警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
+ (void)cancelTask:(NSString *)name
{
if (name.length == 0) return;
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
然后在控制器里调用
#import "ViewController.h"
#import "LLTimer.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"begin");
// selector方式
self.task = [LLTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:YES];
// block方式
// self.task = [LLTimer execTask:^{
// NSLog(@"111111 - %@", [NSThread currentThread]);
// } start:2.0 interval:-10 repeats:NO async:NO];
}
- (void)doTask
{
NSLog(@"doTask - %@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[LLTimer cancelTask:self.task];
}
LLTimer封装细节
- 在
initialize方法
里只执行一次字典的创建和锁的创建(只有用到该类时才创建,并且避免多次调用) - 内部创建一个全局的字典用来保存多个定时器的创建(
定时器的个数递增
作为key,timer
为value) - 外部支持多个参数来控制定时器在哪个线程创建,以及是否只调用一次
- 注意细节的优化,对于传入的时间、是否有任务,以及定时器的标识都对应做校验
- 在多线程环境下,保证创建定时器和取消删除定时器同一时间只能有一个线程在执行
面试题
1.看下面两段代码,会不会造成死锁
// 段1
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
// 段2
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"执行任务2");
});
NSLog(@"执行任务3");
}
第一段会死锁,第二段不会
因为整个函数viewDidLoad
的执行都是在主队列中串行执行的,所以要等函数执行完才会执行任务2,但是dispatch_sync
又是同步的,在主线程中是要执行dispatch_sync
之后才会执行任务3的代码,所以互相之前都要等待,就造成了死锁
而dispatch_async
不会,因为需要等待一会才会执行任务2的代码,所以会先执行任务再执行任务2,不需要马上执行;但是不会开启新的线程
2.看下面这段代码,会不会造成死锁?将队列改为并发队列,会不会死锁
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // block0
NSLog(@"执行任务2");
dispatch_sync(queue, ^{ // block1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
会的。
原因是由于是dispatch_async
,所以会先执行任务1和任务5,然后由于是串行队列,那么先会执行block0
,再执行block1
;但是任务2执行完,后面的是dispatch_sync
,就表示要马上执行任务3,可任务3的执行又是要等block0
执行完才可以,于是就会造成死锁
改为并发队列后不会死锁,虽然都是同一个并发队列,但是可以同时执行多个任务,不需要等待
3.看下面这段代码,会不会造成死锁?将队列2改为并发队列,会不会死锁
NSLog(@"执行任务1");
dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // block0
NSLog(@"执行任务2");
dispatch_sync(queue2, ^{ // block1
NSLog(@"执行任务3");
});
NSLog(@"执行任务4");
});
NSLog(@"执行任务5");
都不会。因为两个任务都是在两个队列里,所以不会有等待情况
4.看下面代码打印结果是什么,为什么,怎么改
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
- (void)test
{
NSLog(@"2");
}
打印1、3。
因为performSelector: withObject: afterDelay:
这个方法是属于RunLoop
的库的,有afterDelay:
参数的本质都是往RunLoop
中添加定时器的,由于当前是在子线程中,不会创建RunLoop
,所以创建RunLoop
后就可以执行该调用,并打印1、3、2
由于该方法的实现RunLoop
是没有开源的,我们要想了解方法实现的本质,可以通过GNUstep
开源项目来查看,这个项目将Cocoa的OC库
重新开源实现了一遍,虽然不是官网源码,但也有一定的参考价值
源码地址:http://www.gnustep.org/resources/downloads.php
我们在官网上找到GNUstep Base
进行下载
然后找到RunLoop.m
中performSelector: withObject: afterDelay:
的实现
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds {
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
找到GSTimedPerformer
的构造方法里面可以看到,会创建一个timer的定时器
,然后将它加到RunLoop
中
- (id) initWithSelector: (SEL)aSelector
target: (id)aTarget
argument: (id)anArgument
delay: (NSTimeInterval)delay
{
self = [super init];
if (self != nil)
{
selector = aSelector;
target = RETAIN(aTarget);
argument = RETAIN(anArgument);
timer = [[NSTimer allocWithZone: NSDefaultMallocZone()]
initWithFireDate: nil
interval: delay
target: self
selector: @selector(fire)
userInfo: nil
repeats: NO];
}
return self;
}
如此一来就印证了我们的分析,下面我们就手动在子线程创建RunLoop
来查看
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
- (void)test
{
NSLog(@"2");
}
创建空的RunLoop
之前因为已经通过performSelector: withObject: afterDelay:
创建了一个定时器加了进去,所以RunLoop
就不为空了,不需要我们再添加一个Source1
了,这样也保证RunLoop
不会退出
运行程序,打印结果为1、3、2
最后打印2是因为RunLoop
被唤醒处理事件有时间延迟,所以会晚一些打印
5.看下面代码打印结果是什么,为什么,怎么改
- (void)test
{
NSLog(@"2");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
打印结果为1,并且崩溃了。
因为执行线程的block
和performSelector
几乎是同时的,所以先执行了block
后的线程就被销毁了,这时再在该线程上发消息就是会报错
解决办法也是创建RunLoop
并让子线程不被销毁
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
6.dispatch_once
是怎么做到只创建一次的,内部是怎么实现的
我们知道GCD
中的dispatch_once
的使用如下代码,可以做的只执行一次,一般用来创建单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"单例应用");
});
我们可以通过源码来分析内部实现,在once.c
中找到dispatch_once
的实现
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
// val是onceToken静态变量
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
其中的_dispatch_Block_invoke
是一个宏定义,用来包装block
#define _dispatch_Block_invoke(bb) \
((dispatch_function_t)((struct Block_layout *)bb)->invoke)
找到其底层是通过dispatch_once_f
实现的
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
// 将外界传入的静态变量val转变为dispatch_once_gate_t类型的变量l
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
// 获取任务标识符v
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
// 如果v == DLOCK_ONCE_DONE,表示任务已经执行过了,直接return
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
// 如果加锁失败走到这里,再次进行存储,并标记为DLOCK_ONCE_DONE
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) { // 尝试进入任务
return _dispatch_once_callout(l, ctxt, func);
}
// 此时已有任务,则进入无限等待
return _dispatch_once_wait(l);
}
dispatch_once_f
函数的详细调用分析
1.os_atomic_load
这个宏用来获取任务标识
#define os_atomic_load(p, m) \
atomic_load_explicit(_os_atomic_c11_atomic(p), memory_order_##m)
2.通过_dispatch_once_mark_done_if_quiesced
进行再次存储和标记
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_mark_done_if_quiesced(dispatch_once_gate_t dgo, uintptr_t gen)
{
if (_dispatch_once_generation() - gen >= DISPATCH_ONCE_GEN_SAFE_DELTA) {
/*
* See explanation above, when the quiescing counter approach is taken
* then this store needs only to be relaxed as it is used as a witness
* that the required barriers have happened.
*/
// 再次存储,并标记为DLOCK_ONCE_DONE
os_atomic_store(&dgo->dgo_once, DLOCK_ONCE_DONE, relaxed);
}
}
3.通过_dispatch_once_gate_tryenter
内部进行比较并加锁
DISPATCH_ALWAYS_INLINE
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
// 进行比较,如果没问题,则进行加锁,并标记为DLOCK_ONCE_UNLOCKED
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
4.通过_dispatch_once_callout
来执行回调
DISPATCH_NOINLINE
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_client_callout
内部就是执行block回调
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
return f(ctxt);
}
_dispatch_once_gate_broadcast
内部会调用_dispatch_once_mark_done
DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l);
#else
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
_dispatch_once_mark_done
内部就是赋值并标记,即解锁
DISPATCH_ALWAYS_INLINE
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
// 如果不相同,则改成相同,并标记为DLOCK_ONCE_DONE
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
总结:
GCD单例中,有两个重要参数,onceToken
和block
,其中onceToken
是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t
类型的变量l
,l
主要是用来获取底层原子封装性的关联,即变量v
,通过v
来查询任务的状态,如果此时v
等于DLOCK_ONCE_DONE
,说明任务已经处理过一次了,直接return
如果此时任务没有执行过,则会在底层通过C++函数
的比较,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK
,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE
,在下次进来时,就不会在执行,会直接返回