ソースコードからatomicがなぜスレッドセキュリティではないのかを理解する

5679 ワード

ネット上でよく見られるエラーの例を修正します.
まず、atomic非スレッドセキュリティに関するネット上でよく見られる例を修正します.スレッドAがgetterを調整し、同時にスレッドB、スレッドCがsetterを調整した場合、最後のスレッドAがgetした値は、B、C set以前の元の値、B setの値、C setの値の3つの可能性があります.また,最終的にこの属性の値は,B setの値であるか,C setの値であるかのいずれかである.したがってatomicは、オブジェクトのスレッドの安全を保証するものではありません.
似たようなこの例は多くの人が見たことがあると信じていますが、とても合理的で、間違いありません.しかし、よく考えてみると、この例自体は問題ありませんが、atomicの非スレッドセキュリティという観点を証明することはできません.だから面接の時にこの例を挙げると~~atomicの非スレッドセキュリティが分からないことを説明します!
  • まず、スレッドが安全ではないことを知っておく必要があります.スレッドの不安全は、マルチスレッドが共有リソースにアクセスし、変更することによって引き起こされる予測不可能な結果です(crashの可能性があります).私たちが得た値が間違っていると簡単に理解できます.この例では、スレッドA getterの値が間違っている値であればスレッドが安全ではないと言えますが、この例ではスレッドAがいくつかの値を取る可能性があるとしても、値を取るのは間違っていると言えますか.できません.だからこの例は間違った例です!*長い間私を誤解させた.次の2つの正しい例を挙げます.

  • atomicの原子性とnonatomicの非原子性
  • atomic:システムが自動的に生成したgetter/setterメソッドはロック操作を行う.読み書きロックを理解することができ、読み書きの安全を保証することができる.時間がかかる.
  • nonatomic:システムが自動的に生成したgetter/setterメソッドはロック操作を行わない.しかし、速度は速くなります.

  • 次は2つのnonatomicとatomic修飾の変数で、私たちはコードでその内部実装を隠します.
    @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の読み書きセキュリティにすぎません.この1つだけですが、スレッドセキュリティには読み書き以外の操作があります.例えば、1つのスレッドがget/setにある場合、別のスレッドがrelease操作を同時に行うと、直接crashになる可能性があります.明らかにatomicの読み書きロックはスレッドの安全を保証できない.次の2つの例は簡単です.
    eg 1:属性NSInteger iが原子であることを定義すると、i=i+1の操作は安全ではありません.原子性は読み書きの安全しか保証できないため、この式は3つの操作を必要とする:1、iの値を読み出してレジスタに格納する;2、iに1を加える.3、iの値を修正する.最初のステップが完了したときにiが他のスレッドによって変更された場合、式の実行結果は予想とは異なり、すなわち安全ではないeg 2:
        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;
            }
        });
    

    結果は[10000020000]の間の値かもしれませんが、私たちが望んでいる結果は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置換:
    using spinlock_t = mutex_tt;
    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
            .
            .
            .