大纲
atomic(原子性)
1、atomic 原理?
2、atomic 修饰的属性绝对安全吗?
自旋锁
一直等待,效率是最高
互斥锁
不会一直等待,会休息,当回调回来的时候才会去再次访问。
atomic 和 nonatomic 是用来修饰属性的。
查看源码 - objc (为什么是看objc 的源码,因为atomic用于属性修饰 setter/getter 底层实现方法是 reallysetPropert)
源码 目录:demos->003-锁分析->1-objc4-756.2源码
void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}
void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}
对比发现,atomic 和 nonatomic 也就第5个参数不一样。tatomic 是true,nonatomic 是 false。到reallySetProperty源码中查看
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 {
//对slot 进行加盐处理,避免哈希冲突
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();//加锁
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
通过源码可以看到,nonatomic setter方法直接进行新值旧值的替换。atomic 加锁之后在进行操作更安全。
这个地方我们看到spinlock_t其实是做了一下转换,真正的锁是os_unfair_lock,
我们再看一下getter方法,搜一下getProperty。
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);
}
我们看到get方法中如果不是atomic 的话,直接返回属性里面的值。如果是atoimc,先加锁,然后将属性retain 得到一个新的变量,然后返回自动释放对象(在合适的地方自动释放)objc_autoreleaseReturnValue()
注意:
atomic 修饰属性加的锁,只是在setter 和 getter 方法内部的锁,保证调用相应方法的 赋值/取值 时的线程安全,但是如果多线程同时访问必不能保证线程安全。
例如:
@interface ViewController ()
@property (atomic) NSInteger number;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 1000; i++) {
self.number++;
NSLog(@"number: %ld", self.number);
}
});
// 线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 1000; i++) {
self.number++;
NSLog(@"number: %ld", self.number);
}
});
}
@end
这里属性是用atomic 修饰的,两个循环各循环1000次,正常来说应该是最终的numer值是2000,但是运行一下发现
2020-04-08 10:17:34.785256+0800 filedome[30625:561172] number: 1872
2020-04-08 10:17:34.785439+0800 filedome[30625:561440] number: 1872
......
2020-04-08 10:17:34.828547+0800 filedome[30625:561172] number: 1972
2020-04-08 10:17:34.828836+0800 filedome[30625:561172] number: 1973
2020-04-08 10:17:34.829159+0800 filedome[30625:561172] number: 1974
2020-04-08 10:17:34.829346+0800 filedome[30625:561172] number: 1975
2020-04-08 10:17:34.829536+0800 filedome[30625:561172] number: 1976
2020-04-08 10:17:34.829713+0800 filedome[30625:561172] number: 1977
通过输出的内容可以发现,在1872 重复了,最终结果也只有1977。这是因为setter 和 getter 内部是加锁了,但是在self.numer++ 的时候没有加锁,这时候多条线程可以同时访问,setter 和 getter 方法。如果两条线程同时获取了同一个值,+操作后的结果就是一样的,所以会出现重复。
总结:
1、atomic 通过在setter、getter 内部进行加锁保证其内部线程安全
2、atomic 不是绝对安全的。atomic 修饰的属性在setter、getter 方法可以保证线程安全,但是在外部不能保证线程安全。同时因为其setter、getter方法内部有加锁、解锁的操作,所以其性能没有nonatomic高。