JHHK

欢迎来到我的个人网站
行者常至 为者常成

Atomic的安全性

目录

原子操作

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 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)}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.