《iOS面试之道》读书笔记 - atomic/nonatomic

atomicnonatomic用于在定义 property 时指明其原子性:

  • atomic表示是原子性的,调用该 property 的 getter 和 setter 会保证对象的完整性。多线程操作时,任何调用都可以得到一个完整的对象,因此速度较慢。
  • nonatomic表示是原子性的,调用该 property 的 getter 和 setter 不保证对象的完整性。多个线程对它进行访问,它可能会返回未初始化的对象,因此速度较快。

注意,以上讨论仅对编译器自动生成的 getter/setter 有效。如果你自己实现了 getter/setter,无论使用 atomic/nonatomic,property 的原子性都将取决于你自己的实现。

使用 atomic 修饰的 property 是如何保证原子性的?

对于任何没有手动实现的属性,编译器都会生成一个objc_getProperty_non_gc的函数作为 getter,同时生成一个objc_setProperty_non_gc的函数作为 setter。

objc_getProperty_non_gc函数的源代码可以看到[1]

// objc-accessors.mm

id objc_getProperty_non_gc(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);
}

objc_setProperty_non_gc函数最终将调到reallySetProperty函数[2]

// objc-accessors.mm

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);
}

其中处理 atomic 的分支里,getter/setter 都使用了在PropertyLocks中的128个自旋锁(Spinlock)中的1个来给操作上锁。这是一种务实和快速的方式,最糟糕的情况下,如果遇到了哈希碰撞,那么 setter 需要等待另一个和它无关的 setter 完成之后再进行工作[3]

使用 atomic 修饰的 property 是线程安全的吗?

考虑对一个 nonatomic 的整形变量integer进行加1操作,可分为三个步骤:首先从内存中取出原始值,然后加1,最后将修改后的值写回内存。
现在有AB两个并发线程,分别对integer进行加1操作,那么问题来了:两个线程都从变量中取出了原始值,假设17,然后A将值加1,然后将结果18写回内存;同时B也将值加1后将结果18写回内存。此时integer被加1了两次,但最终值却是18

《iOS面试之道》读书笔记 - atomic/nonatomic_第1张图片
image

这个问题称为竞态条件,atomic 通过在读写时加入自旋锁,能保证对象的完整性,保护你免于在 setter 中遭遇到竞态条件的困扰。
但这并代表使用 atomic 就是线程安全的。考虑三个并发线程ABC,其中AB对变量integer进行加1操作,C从变量integer中读取数据。因为三个线程是并发的,所以C读取数据的时机可能在其他两个写入数据的线程AB之前,或在他们两个之间,也可能在他们两个之后,导致C读到的数据可能是171819

如何保证 property 的线程安全性?

没有银弹,具体问题具体分析。@synchronizeddispatch_queue都是可选的解决方案,但也都各有利弊。这里就不展开说明了,那将又是一大块篇幅。


  1. objc-accessors.mm - objc_getProperty_non_gc ↩

  2. objc-accessors.mm - reallySetProperty ↩

  3. 线程安全类的设计 ↩

你可能感兴趣的:(《iOS面试之道》读书笔记 - atomic/nonatomic)