首先看一段代碼
@interface ViewController ()
@property (nonatomic,strong)NSObject *obj;
@end
@implementation KVOViewController
- (void)viewDidLoad {
for (NSUInteger i =0 ; i <10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self set]
if (i == 9999) {
NSLog(@"執(zhí)行完成");
}
});
}
}
@end
這段代碼在執(zhí)行過程中會(huì)出現(xiàn)EXC_BAD_ACCESS
類型的crash,很多人都知道這個(gè)由于在多個(gè)線程中操作同一個(gè)數(shù)據(jù)區(qū)引起不安全的問題趁餐。
根本原因在于在屬性obj的賦值不是原子性,我們知道在ARC機(jī)制下菇绵,對(duì)屬性的賦值等價(jià)于MRC的如下代碼:
- (void)setObj:(NSObject *)obj{
if (_obj) {
[_obj release];
_obj = nil;
}
_obj = [obj retain];
}
由于不是原子性肄渗,導(dǎo)致兩個(gè)線程同時(shí)進(jìn)入到setObj:方法,有可能對(duì)_obj變量同時(shí)釋放兩次咬最。這種類型的問題用Xcode的 Thread Sanitizer線程檢查工具檢查出來翎嫡,Xcode稱這類問題為Data Race.
使用Xcode 的Thread Sanitizer工具詳見:Thread Sanitizer and Static Analysis
上述只是一個(gè)極端例子,其實(shí)平時(shí)開發(fā)中永乌,我們往往會(huì)忽略代碼中隱藏的Data Race問題惑申,特別是某個(gè)公共庫的方法可能被多次調(diào)用時(shí),下面是比較常見的例子:
typedef void (^GlobalBlock) (NSMutableDictionary *dic);
+ (void)setConfigType:(GlobalBlock) newParamsBlock {
static GlobalBlock block = nil;
static BOOL isInitilized = NO;
if (isInitilized) {
isInitilized = YES;
block = newParamsBlock;
}
}
例如以上方法是一個(gè)類方法翅雏,該方法隱含定義了兩個(gè)靜態(tài)變量圈驼,由于靜態(tài)變量的生命周期是整個(gè)APP運(yùn)行期間。如果該方法在多個(gè)線程中被同時(shí)調(diào)用望几,則可能引發(fā)Data Race問題绩脆。
上述問題可以通過加鎖的方式解決,如下:
+ (void)setConfigType:(GlobalBlock) newParamsBlock {
static GlobalBlock block = nil;
static BOOL isInitilized = NO;
@synchronized(self){
if (isInitilized) {
isInitilized = YES;
block = newParamsBlock;
}
}
}