美文网首页
《iOS面试之道》读书笔记 - atomic/nonatomic

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

作者: ltryee | 来源:发表于2018-09-07 17:31 被阅读43次

    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

    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

          本文链接:https://www.haomeiwen.com/subject/pgocgftx.html