在實際的開發(fā)中遇到了一個有趣的問題,在我用 nonatomic 定義的對象中
@property (nonatomic, strong)
出現(xiàn)了崩潰哗伯,崩潰原因是在子線程Thread4上晶乔,對象釋放了。于是果然重寫他的setter方法來查看問題:
- (void)setFunction:(NSString *)function
{
if (_function != function) {
_function = function;
}
}
再修改了setter方法之后發(fā)現(xiàn)依然崩潰:
這次是在Thread2 中發(fā)現(xiàn)了崩潰,多運行幾次發(fā)現(xiàn)這些崩潰線程是無序的,果然該問題是與線程有關(guān)。
我們新建個項目來分析遇到的這個問題,
@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];
});
}
通過異步線程反復(fù)執(zhí)行發(fā)現(xiàn),果然與我們遇到的問題相同,對象被提前釋放了,可是我們的setter方法中并沒有釋放過對象,深入研究后發(fā)現(xiàn)原來在MRC上setter方法如下:
-(void)setFunction:(NSString *)function{
if (_function != function) {
[_function release];
[function retain];
_function = function;
}
}
這說明 雖然在ARC模式下不用寫其set方法,但是在我們執(zhí)行setter方法的時候還是會和ARC相同蔓榄。
因為是多線程,且沒有加鎖保護默刚,所以在一個線程走到[_function release]后甥郑,可能在另一個線程又一次去釋放,這時候造成崩潰荤西。
果斷改變思路將修飾符改成了atomic澜搅,發(fā)現(xiàn)此時已經(jīng)不崩潰了伍俘。
那么問題來了,使用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下執(zhí)行完畢的最后一行,如果是絕對的線程安全,即使在異步線程下,最后一遍執(zhí)行的NSLog 出來的number值也應(yīng)該是是20000 才對,直接看控制臺的結(jié)果:
2020-04-01 14:05:19.671949+0800 copy[29072:2224902] Thread 1: 18161
為什么結(jié)果是18161勉躺,多執(zhí)行幾次后發(fā)現(xiàn)最后一次輸出的是無序的數(shù)字癌瘾。
所以線程是不安全的。thread1 在執(zhí)行表達式 self.number之后 self.number = self.number + 1;并沒有執(zhí)行完畢饵溅。此時thread2 執(zhí)行self.number = self.number + 1;再回到thread1時妨退,self.number的數(shù)值就被更新了;所以僅僅使用atomic并不能保證線程安全概说。
atomic 只能保證屬性的存取方法是線程安全的,多線程下將屬性設(shè)置為atomic可以保證讀取數(shù)據(jù)的一致性碧注。因為他將保證數(shù)據(jù)只能被一個線程占用嚣伐,也就是說一個線程對屬性進行寫操作時糖赔,會使用自旋鎖鎖住該屬性。不允許其他的線程對其進行讀取操作了轩端。而且因為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];
});
再看輸出框 果然和我們預(yù)想的一樣:
同樣的方法還有以下:
os_unfair_lock(推薦??????????)
OSSpinLock(不安全????)
dispatch_semaphore(推薦??????????)
pthread_mutex(推薦????????)
dispatch_queue(DISPATCH_QUEUE_SERIAL)(推薦??????)
NSLock(??????)
NSCondition(??????)
pthread_mutex(recursive)(????)
NSRecursiveLock(????)
NSConditionLock(????)
@synchronized(最不推薦)
使用方式可以看: 如何保證iOS的多線程安全)