美文网首页
『ios』atomic真的是线程安全吗?

『ios』atomic真的是线程安全吗?

作者: butterflyer | 来源:发表于2021-06-01 21:05 被阅读0次

    似懂非懂的东西,如果别人问你为什么,你说不出个所以然来,最好还是花些时间来搞懂它。不要觉得现在没人问你,相信我,这些坑早晚有一天会踩到,踩到的时候,失去的可能不仅仅是一个机会了。

    什么是线程安全???
    线程安全:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。

    我们都知道 atomic 原子性 和 nonatomic非原子性

    atomic :系统自动生成的getter/setter方法会进行加锁操作;可以理解过读写锁,可以保证读写安全;较耗时;
    nonatomic : 系统自动生成的getter/setter方法不会进行加锁操作;但速度会更快;
    

    对于这两种的生成方式,我觉得看代码会更加直观。

    @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和线程安全放到一起去探究呢?
    我们在代码中可以看到,我们对他的set get方法进行了加锁操作。但是这样紧紧只是保证了读写线程安全,但是真正的线程安全还要包含其他的操作,比如release。如果在当前线程访问另一个线程已经release的变量,相信我可能会crash掉的,那么很明显,atomic并不能真的保证线程安全。

    nonatomic MRC下的set 方法

    -(void)setName:(NSString *)name{
        if (_name != name) {
            [_name release];
            [name retain];
            _name = name;
        }
    }
    

    下面来看这么一段代码

    - (void)atomic{
            self.number = 0;
            dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
                self.number ++;
            });
            NSLog(@"_number:%d", self.number);
        }
    

    你觉得打印出来self.number是什么呢?是10000吗?还是其他数。
    答案是其他数。

    number++等价于
     int temp = _number+1;
     _number = temp;
    

    虽然atomic保证了number属性线程安全了,但是并不能保证temp变量的线程安全,又因为是多线程的,所以有可能同时执行多次 int temp = _number+1;才执行一次 _number = temp;导致结果每次都不同,而且结果不可预知。

    然后我们来看一下atomic的源码

    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替代。而实际上也真的已经被替换。

    什么是线程安全呢?
    线程安全:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。

    那么最后来总结下
    nonatomic肯定不是线程安全的
    atomic修饰后,我们为这个对象加了锁, 不会出现多线程同时修改这个值的。至于这个值最终是什么,无法确定,是因为你不知道多线程的调用顺序,也就无法判断最终的值是什么。
    Atomic不能保证对象多线程的安全,它只是能保证你访问的时候给你返回一个完好无损的Value而已。atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的
    如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。
    atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。

    相关文章

      网友评论

          本文标题:『ios』atomic真的是线程安全吗?

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