美文网首页
atomic和nonatomic与线程安全

atomic和nonatomic与线程安全

作者: 大成小栈 | 来源:发表于2021-07-16 11:31 被阅读0次

    1. atomic 和 nonatomic 的区别

    首先,我们看以下代码,第一行是nonatomic修饰的,非原子操作;后两行是一样的,不写的话默认就是atomic

    @property(nonatomic, strong) UITextField *userName;
    @property(atomic, strong) UITextField *userName;
    @property(strong) UITextField *userName;
    

    atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样。

    对于 atomic 的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。

    而 nonatomic 就没有这个保证了。所以,nonatomic 的速度要比 atomic 快。

    不过 atomic 只能保证对象级的原子操作,可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。

    • Atomic
      速度不快,因为要保证操作整体完成;
      会保证多线程访问这个属性get、set 方法的原子操作,属性是读/写安全的。
    @property(retain) UITextField *userName;
    
    // 系统生成的代码如下:
    - (UITextField *) userName {
        UITextField *retval = nil;
        @synchronized(self) {
            retval = [[userName retain] autorelease];
        }
        return retval;
    }
    - (void) setUserName:(UITextField *)userName_ {
        @synchronized(self) {
          [userName release];
          userName = [userName_ retain];
        }
    }
    
    • Non-Atomic
      更快,但线程不安全;
      若两个线程频繁同时访问同一属性,结果无法预料。
    @property(nonatomic, retain) UITextField *userName;
    
    //系统生成的代码如下:
    - (UITextField *) userName {
        return userName;
    }
    - (void) setUserName:(UITextField *)userName_ {
        [userName_ retain];
        [userName release];
        userName = userName_;
    }
    

    简单来说,就是 atomic 会加一个锁来保障线程安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调 setter,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。

    atomic虽然能保证属性是读/写安全的,但是在多线程get、set的同时,如果有另一个线程 D 同时调用 [name release],那可能就会发生crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。如果一个对象的改变不是直接调用 getter/setter 方法,而是直接对对象内部属性修改、字符串拼接、数组增加和移除元素等操作,就不能保证这个对象是线程安全的。线程安全需要开发者自己来保证。

    2. 实例一

    // nonatomic属性
    @property (nonatomic, strong) NSString *name;
    // atomic属性
    @property (atomic, assign) int number;
    

    对于 name 属性使用 nonatomic 修饰:

    // 10000个异步任务,修改name属性的值
    - (void)nonatomic{
        for (NSInteger i = 0; i < 10000; i++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                self.name = [NSString stringWithFormat:@"name:%ld", i];
            });
        }
    }
    

    执行结果:崩溃,崩溃原因是在子线程Thread8上,对象释放了。

    1. 在nonatomic属性name的set方法中有[_name release]操作,多线程进入且没加锁;
    2. 在多个线程同时走过[_name release],二次release会造成崩溃;
    3. 将属性的nonatomic改为atomic就不会出现崩溃了。

    number属性使用atomic修饰:

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

    执行结果:执行结果并不是10000,而且每次运行结果都不一样,即运行结果不可预见。


    在多个线程同时读取了self.number及其值,++后又set回去这一过程中,atomic保证了number属性读、写操作的线程安全,但是并没有保证读/写的整个逻辑过程在多异步线程间的同步。也就是说,多线程间并没有给读/写的整个逻辑过程加锁。

    3. 实例二

    NSMutableArray 和NSMutableDictionary 都不是线程安全的,主线程我们多次操作 都没有问题,但多线程下短时间内有大量的读写操作会引起数据的错乱导致crash。

    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSMutableArray *originArray = [NSMutableArray new];
    for (int i = 0 ; i < 1000; i++) {
        dispatch_async(quene, ^{
            [originArray addObject:[NSString stringWithFormat:@"item%ld", i]];
        });
    }
    

    这样直接会crash,怎样解决呢?加个 @synchronized 就可以解决:

     for (int i = 0 ; i < 1000; i++) {
            dispatch_async(quene, ^{
                @synchronized (originArray) {
                    [originArray addObject:[NSString stringWithFormat:@"item%ld", i]];
                }
            });
        }
    

    但是synchronized效率比较低。关于 NSMutableArray 的读写 安全高效的做法是这样的在读的时候我们使用GCD同步机制,写的时候使用GCD的Barrier:

    //模仿的写操作
    - (void)addItem:(id)item {
        dispatch_barrier_async(self.readWriteQuene, ^{
            [self.array addObject:item];
        });
    }
    
    //模仿的读操作
    - (id)getLastItem {
        __block id item = nil;
        dispatch_sync(self.readWriteQuene, ^{
            NSUInteger size = self.array.count;
            if (size > 0) {
                item = self.array[size - 1];
            }
        });
        return item;
    }
    

    当然,我们还可以对NSMutableArray加以封装,使其他相关操作都加上GCD同步机制。一般插入、添加、替换、删除等使用dispatch_barrier_async约束,而读取类操作使用dispatch_sync即可,原因不言而喻。

    参考文章:
    https://www.jianshu.com/p/57ccbf5c704b
    https://blog.csdn.net/shifang07/article/details/100576587
    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html

    相关文章

      网友评论

          本文标题:atomic和nonatomic与线程安全

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