美文网首页
IOS nonatomic 与atomic 分析

IOS nonatomic 与atomic 分析

作者: 多喝热开水 | 来源:发表于2020-04-01 14:26 被阅读0次

    在实际的开发中遇到了一个有趣的问题,在我用 nonatomic 定义的对象中

    @property (nonatomic, strong) 
    
    image.png

    出现了崩溃,崩溃原因是在子线程Thread4上,对象释放了。于是果然重写他的setter方法来查看问题:

    - (void)setFunction:(NSString *)function
    {
        if (_function != function) {
            _function = function;
        }
    }
    

    再修改了setter方法之后发现依然崩溃:


    image.png

    这次是在Thread2 中发现了崩溃,多运行几次发现这些崩溃线程是无序的,果然该问题是与线程有关。
    我们新建个项目来分析遇到的这个问题,

    @property (nonatomic, strong) NSString *function;
    
    for (NSInteger i = 0; i < 10000; i++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                self.function = [NSString stringWithFormat:@"fucntion:%ld", i];
            });
        }
    

    通过异步线程反复执行发现,果然与我们遇到的问题相同,对象被提前释放了,可是我们的setter方法中并没有释放过对象,深入研究后发现原来在MRC上setter方法如下:

    -(void)setFunction:(NSString *)function{
        if (_function != function) {
            [_function release];
            [function retain];
            _function = function;
        }
    }
    

    这说明 虽然在ARC模式下不用写其set方法,但是在我们执行setter方法的时候还是会和ARC相同。
    因为是多线程,且没有加锁保护,所以在一个线程走到[_function release]后,可能在另一个线程又一次去释放,这时候造成崩溃。
    果断改变思路将修饰符改成了atomic,发现此时已经不崩溃了。

    那么问题来了,使用atomic就是绝对的线程安全么?

    @property (atomic, assign) int number;
    //线程1
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 10000; i ++){
            self.number = self.number + 1;
            NSLog(@"Thread 1: %d\n", self.number);
        }
    });
        
        //线程2
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 10000; i ++){
            self.number = self.number + 1;
            NSLog(@"Thread 2: %d\n", self.number);
         }
     });
    

    看console下执行完毕的最后一行,如果是绝对的线程安全,即使在异步线程下,最后一遍执行的NSLog 出来的number值也应该是是20000 才对,直接看控制台的结果:

    2020-04-01 14:05:19.671949+0800 copy[29072:2224902] Thread 1: 18161
    

    为什么结果是18161,多执行几次后发现最后一次输出的是无序的数字。
    所以线程是不安全的。thread1 在执行表达式 self.number之后 self.number = self.number + 1;并没有执行完毕。此时thread2 执行self.number = self.number + 1;再回到thread1时,self.number的数值就被更新了;所以仅仅使用atomic并不能保证线程安全。
    atomic 只能保证属性的存取方法是线程安全的,多线程下将属性设置为atomic可以保证读取数据的一致性。因为他将保证数据只能被一个线程占用,也就是说一个线程对属性进行写操作时,会使用自旋锁锁住该属性。不允许其他的线程对其进行读取操作了。而且因为atomic要使用自旋锁锁住该属性,因此它会消耗更多的资源,性能会很低。要比nonatomic慢20倍。

    所以我们需要对线程安全时需要怎么做:

    NSLock

        NSLock *_lock = [[NSLock alloc] init];
        //线程1
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [_lock lock];
            for (int i = 0; i < 10000; i ++){
                self.number = self.number + 1;
                NSLog(@"Thread 1: %d\n", self.number);
            }
            [_lock unlock];
        });
        
        //线程2
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [_lock lock];
            for (int i = 0; i < 10000; i ++){
                self.number = self.number + 1;
                NSLog(@"Thread 2: %d\n", self.number);
            }
            [_lock unlock];
        });
    

    再看输出框 果然和我们预想的一样:


    image.png

    同样的方法还有以下:
    os_unfair_lock(推荐🌟🌟🌟🌟🌟)
    OSSpinLock(不安全⚠️⚠️)
    dispatch_semaphore(推荐🌟🌟🌟🌟🌟)
    pthread_mutex(推荐🌟🌟🌟🌟)
    dispatch_queue(DISPATCH_QUEUE_SERIAL)(推荐🌟🌟🌟)
    NSLock(🌟🌟🌟)
    NSCondition(🌟🌟🌟)
    pthread_mutex(recursive)(🌟🌟)
    NSRecursiveLock(🌟🌟)
    NSConditionLock(🌟🌟)
    @synchronized(最不推荐)

    使用方式可以看: 如何保证iOS的多线程安全)

    相关文章

      网友评论

          本文标题:IOS nonatomic 与atomic 分析

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