atomic和nonatomic与线程安全

2021-07-16  本文已影响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 的值。

@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];
    }
}
@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

上一篇下一篇

猜你喜欢

热点阅读