目录
原子操作
所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。
如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构,那么这个操作是一个原子(atomic)操作。 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。 将整个操作视作一个整体是原子性的核心特征。
@property (nonatomic, strong) NSString * name;
@property (atomic , strong) NSString * height;
在Objective-C中,有两个修饰词nonatomic、atomic,对应的是非原子操作和原子操作。 绝大部分 Objective-C 程序员使用属性时,一般都使用其非默认缺省的状态,也就是 nonatomic。 因为nonatomic的执行效率更高,但在多线程下安全性得不到保障。atomic一般认为是线程安全的,但执行效率较低。
那么atomic的线程安全究竟怎样呢?我们先看一段代码
@interface Person : NSObject<NSCopying>
@property (atomic, assign) int count;
@end
Person * p = [[Person alloc] init];
p.count = 0;
dispatch_async(dispatch_queue_create("CONCURRENT1", DISPATCH_QUEUE_CONCURRENT), ^{
for (int i=0; i<100000; i++) {
p.count ++;
}
NSLog(@" p.count = %d", p.count);
NSLog(@"currentThread = %@",[NSThread currentThread]);
});
dispatch_async(dispatch_queue_create("CONCURRENT2", DISPATCH_QUEUE_CONCURRENT), ^{
for (int i=0; i<100000; i++) {
p.count ++;
}
NSLog(@"p.count = %d",p.count);
NSLog(@"currentThread = %@",[NSThread currentThread]);
});
控制台
p.count = 79597
currentThread = <NSThread: 0x2839f4040>{number = 4, name = (null)}
p.count = 126631
currentThread = <NSThread: 0x2839e9240>{number = 5, name = (null)}
可以看出atomic在多线程下,并非完全的线程安全。
原理
查看objc源码
static inline void reallySetProperty(id self, SEL _cmd,
id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) {
//偏移为0说明改的是isa
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);
}
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
// 取isa
if (offset == 0) {
return object_getClass(self);
}
// 非原子操作直接返回
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// 原子操作自旋锁
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// 出于性能考虑,在锁之外autorelease
return objc_autoreleaseReturnValue(value);
}
属性的读取操作在atomic下都使用了锁来保证线程的安全,但读时的锁和存时的锁不是同一把,在读取组合操作时,线程就不是安全的了。所以单独的原子操作绝对是线程安全的,但是组合一起的操作就不能保证。
解决的办法应该是增加颗粒度,将读写两个操作合并为一个原子操作,从而解决写入过期数据的问题。
Person * p = [[Person alloc] init];
p.count = 0;
dispatch_async(dispatch_queue_create("CONCURRENT1", DISPATCH_QUEUE_CONCURRENT), ^{
for (int i=0; i<100000; i++) {
@synchronized ([self class]) {
p.count ++;
}
}
NSLog(@" p.count = %d", p.count);
NSLog(@"currentThread = %@",[NSThread currentThread]);
});
dispatch_async(dispatch_queue_create("CONCURRENT2", DISPATCH_QUEUE_CONCURRENT), ^{
for (int i=0; i<100000; i++) {
@synchronized ([self class]) {
p.count ++;
}
}
NSLog(@"p.count = %d",p.count);
NSLog(@"currentThread = %@",[NSThread currentThread]);
});
控制台
p.count = 185369
currentThread = <NSThread: 0x28079c8c0>{number = 6, name = (null)}
p.count = 200000
currentThread = <NSThread: 0x2807acd80>{number = 5, name = (null)}
行者常至,为者常成!