在多線程操作過程中,往往一個(gè)數(shù)據(jù)同時(shí)被多個(gè)線程讀寫,在這種情況下,如果沒有相應(yīng)的機(jī)制對(duì)數(shù)據(jù)進(jìn)行保護(hù),就很可能會(huì)發(fā)生數(shù)據(jù)污染的的問題,給程序造成各種難以重現(xiàn)的潛在bug.
多線程的安全隱患
下面是一個(gè)模擬的會(huì)導(dǎo)致奔潰的程序代碼
- (void)viewDidLoad
{
[self configData];
}
- (void)configData
{
self.dataSource = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
[self.dataSource addObject:[NSString stringWithFormat:@"Obj - %i", i]];
}
}
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
[self.dataSource removeAllObjects];
}
用戶在點(diǎn)擊start按鈕后,會(huì)在一個(gè)全局的queue里面對(duì)構(gòu)造的數(shù)據(jù)進(jìn)行遍歷,為了模擬實(shí)際場(chǎng)景中網(wǎng)絡(luò)請(qǐng)求的時(shí)延,每次循環(huán)讓當(dāng)前線程休息0.05s,這樣在遍歷的過程中,如果用戶點(diǎn)擊了移除按鈕,此時(shí)self.dataSource[i]執(zhí)行時(shí),因?yàn)閿?shù)組已經(jīng)被清空了,會(huì)報(bào)數(shù)組越界的錯(cuò)誤。
如何解決
在多線程操作過程中,如何保護(hù)共享數(shù)據(jù),其實(shí)已經(jīng)是一個(gè)眾所周知的事情了,這里總結(jié)下自己試過的處理方法:
@synchronizedNSLockdispatch_semaphore_signaldispatch_barrier_async
下面是使用@synchronized修復(fù)的示例:
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(self.dataSource) {
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@",self.dataSource[i]);
}
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
@synchronized(self.dataSource) {
[self.dataSource removeAllObjects];
}
}
下面是使用NSLock修復(fù)的示例:
//聲明一個(gè)全局變量
NSRecursiveLock* rLock = [[NSRecursiveLock alloc] init];
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[rLock lock];
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
[rLock unlock];
});
}
- (IBAction)removeAllObjs:(id)sender
{
[rLock lock];
[self.dataSource removeAllObjects];
[rLock unlock];
}
下面是使用dispatch_semaphore_signal修復(fù)的示例:
//聲明全局變量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@",self.dataSource[i]);
}
dispatch_semaphore_signal(semaphore);
});
}
- (IBAction)removeAllObjs:(id)sender
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self.dataSource removeAllObjects];
dispatch_semaphore_signal(semaphore);
}
下面是使用dispatch_barrier_async修復(fù)的示例:
//聲明全局變量
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.threadsafe.sing", DISPATCH_QUEUE_CONCURRENT);
- (IBAction)start:(id)sender
{
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < self.dataSource.count; i++) {
[NSThread sleepForTimeInterval:0.05];
NSLog(@"%@", self.dataSource[i]);
}
});
}
- (IBAction)removeAllObjs:(id)sender
{
dispatch_barrier_async(concurrentQueue, ^{
[self.dataSource removeAllObjects];
});
}