IOS nonatomic 与atomic 分析
在实际的开发中遇到了一个有趣的问题,在我用 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的多线程安全)