從源代碼理解atomic爲何不是線程安全

糾正一個網上常見的錯誤的例子:

開文首先我要糾正一個網上常見的關於atomic非線程安全的舉例:若是線程 A 調了 getter,與此同時線程 B 、線程 C 都調了 setter——那最後線程 A get 到的值,有3種可能:多是 B、C set 以前原始的值,也多是 B set 的值,也多是 C set 的值。同時,最終這個屬性的值,多是 B set 的值,也有多是 C set 的值。因此atomic可並不能保證對象的線程安全。面試

相似的這個例子相信不少人都見過,看起來也很是合理,沒什麼錯;但細琢磨,這個例子自己沒問題,但根本不能證實atomic的非線程安全這個觀點!因此面試的時候若是舉這個例子~~說明你就沒明白atomic的非線程安全性!安全

  • 首先你得知道什麼是線程不安全,線程的不安全是因爲多線程訪問和修改共享資源而引發的不可預測的結果(有可能crash)。能夠簡單理解爲咱們拿到的值是錯的。這個例子中,若是線程A getter到的值是個錯誤的值才能說是線程不安全的,但是這個例子就算線程A可能取到好幾種值,你能說取值不對嗎;不能。因此這個例子是個錯誤的例子!*誤導了我很久;下文中我會舉兩個正確的例子。

atomic的原子性和nonatomic的非原子性

  • atomic :系統自動生成的getter/setter方法會進行加鎖操做;能夠理解過讀寫鎖,能夠保證讀寫安全;較耗時;
  • nonatomic : 系統自動生成的getter/setter方法不會進行加鎖操做;但速度會更快;\

下面是兩個nonatomic和atomic修飾的變量,咱們用代碼掩飾其內部實現;bash

@property (nonatomic) UIImage *nonImage;
@property (atomic) UIImage *atomicImage;

//nonatomic的setter和getter實現:
- (void)setNonImage:(UIImage *)nonImage
{
    _nonImage = nonImage;
}
- (UIImage *)nonImage
{
    return _nonImage;
}
//atomic的setter和getter實現:
- (void)setAtomicImage:(UIImage *)atomicImage
{
    @synchronized (self) {
        _atomicImage = atomicImage;
    }
}
- (UIImage *)atomicImage
{
    @synchronized (self) {
        return _atomicImage;
    }
}

複製代碼

源代碼分析atomic爲何不是線程安全

其實如今一想很奇怪,爲何要把atomic和線程安全聯繫在一塊兒去探究;atomic只是對屬性的getter/setter方法進行了加鎖操做,這種安全僅僅是get/set的讀寫安全,僅此之一,可是線程安全還有除了讀寫的其餘操做,好比:當一個線程正在get/set時,另外一個線程同時進行release操做,可能會直接crash。很明顯atomic的讀寫鎖不能保證線程安全。 下面兩個例子寫的就挺好,挺簡單:\多線程

eg1:若是定義屬性NSInteger i是原子的,對i進行i = i + 1操做就是不安全的; 由於原子性只能保證讀寫安全,而該表達式須要三步操做:
一、讀取i的值存入寄存器;
二、將i加1;
三、修改i的值;
若是在第一步完成的時候,i被其餘線程修改了,那麼表達式執行的結果就與預期的不同,也就是不安全的 eg2:async

self.slice = 0;
    dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<10000; i++) {
            self.slice = self.slice + 1;
        }
    });
    dispatch_async(queue, ^{
        for (int i=0; i<10000; i++) {
            self.slice = self.slice + 1;
        }
    });
複製代碼

結果多是[10000,20000]之間的某個值,而咱們想要的結果是20000;很明顯這個例子就會引發線程隱患,而atomic並不能防止這個問題;因此咱們說atomic不是線程安全;
因此要想真正理解atomic的非線程安全性,必需要去官網查找解釋並經過源碼分析才行; 在runtime時property的atomic是一個booleau值,是採用spinlock_t鎖去實現的;源碼分析

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);
}
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屬性的setter/getter方法都被加了spinlock自旋鎖,須要注意的是spinlock已經因爲存在優先級反轉問題被棄用並用os_unfair_lock替代。既然被棄用了,這裏爲何還在用;緣由是進入spinlock去看會發現,底層已經被os_unfair_lick替換:ui

using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
 public:
    constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
        lockdebug_remember_mutex(this);
    }

    constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }

    void lock() {
        lockdebug_mutex_lock(this);

        os_unfair_lock_lock_with_options_inline
        .
        .
        .
複製代碼
相關文章
相關標籤/搜索