36、iOS底层分析 - 线程锁(五)- 自旋锁 atomic

大纲

atomic(原子性)
 1、atomic 原理?
 2、atomic 修饰的属性绝对安全吗?

自旋锁
 一直等待,效率是最高
互斥锁
 不会一直等待,会休息,当回调回来的时候才会去再次访问。
 
 atomic 和 nonatomic 是用来修饰属性的。
 查看源码 - objc (为什么是看objc 的源码,因为atomic用于属性修饰 setter/getter 底层实现方法是 reallysetPropert)
 源码 目录:demos->003-锁分析->1-objc4-756.2源码

    void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
    {
        reallySetProperty(self, _cmd, newValue, offset, true, false, false);
    }
    
    void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
    {
        reallySetProperty(self, _cmd, newValue, offset, false, false, false);
    }

对比发现,atomic 和 nonatomic 也就第5个参数不一样。tatomic 是true,nonatomic 是 false。到reallySetProperty源码中查看

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 {
            //对slot 进行加盐处理,避免哈希冲突
            spinlock_t& slotlock = PropertyLocks[slot];
            slotlock.lock();//加锁
            oldValue = *slot;
            *slot = newValue;
            slotlock.unlock();
        }
        
        objc_release(oldValue);
    }

 通过源码可以看到,nonatomic setter方法直接进行新值旧值的替换。atomic 加锁之后在进行操作更安全。

 
 这个地方我们看到spinlock_t其实是做了一下转换,真正的锁是os_unfair_lock,
 我们再看一下getter方法,搜一下getProperty。

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

  我们看到get方法中如果不是atomic 的话,直接返回属性里面的值。如果是atoimc,先加锁,然后将属性retain 得到一个新的变量,然后返回自动释放对象(在合适的地方自动释放)objc_autoreleaseReturnValue()
 

 注意:

atomic 修饰属性加的锁,只是在setter 和 getter 方法内部的锁,保证调用相应方法的 赋值/取值 时的线程安全,但是如果多线程同时访问必不能保证线程安全。
 例如:

@interface ViewController ()
    @property (atomic) NSInteger number;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 线程1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 1000; i++) {
                self.number++;
                NSLog(@"number: %ld", self.number);
            }
        });
        
        // 线程2
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 1000; i++) {
                self.number++;
                NSLog(@"number: %ld", self.number);
            }
        });
    }
@end

这里属性是用atomic 修饰的,两个循环各循环1000次,正常来说应该是最终的numer值是2000,但是运行一下发现

 2020-04-08 10:17:34.785256+0800 filedome[30625:561172] number: 1872
 2020-04-08 10:17:34.785439+0800 filedome[30625:561440] number: 1872
 ......
 2020-04-08 10:17:34.828547+0800 filedome[30625:561172] number: 1972
 2020-04-08 10:17:34.828836+0800 filedome[30625:561172] number: 1973
 2020-04-08 10:17:34.829159+0800 filedome[30625:561172] number: 1974
 2020-04-08 10:17:34.829346+0800 filedome[30625:561172] number: 1975
 2020-04-08 10:17:34.829536+0800 filedome[30625:561172] number: 1976
 2020-04-08 10:17:34.829713+0800 filedome[30625:561172] number: 1977

通过输出的内容可以发现,在1872 重复了,最终结果也只有1977。这是因为setter 和 getter 内部是加锁了,但是在self.numer++ 的时候没有加锁,这时候多条线程可以同时访问,setter 和 getter 方法。如果两条线程同时获取了同一个值,+操作后的结果就是一样的,所以会出现重复。


 总结:

1、atomic 通过在setter、getter 内部进行加锁保证其内部线程安全
2、atomic 不是绝对安全的。atomic 修饰的属性在setter、getter 方法可以保证线程安全,但是在外部不能保证线程安全。同时因为其setter、getter方法内部有加锁、解锁的操作,所以其性能没有nonatomic高。

发布了104 篇原创文章 · 获赞 13 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/105381184
今日推荐