在多线程操作过程中,往往一个数据同时被多个线程读写,在这种情况下,如果没有相应的机制对数据进行保护,就很可能会发生数据污染的的问题,给程序造成各种难以重现的潜在bug.
多线程的安全隐患
下面是一个模拟的会导致奔溃的程序代码
- (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];
}
用户在点击start按钮后,会在一个全局的queue里面对构造的数据进行遍历,为了模拟实际场景中网络请求的时延,每次循环让当前线程休息0.05s,这样在遍历的过程中,如果用户点击了移除按钮,此时self.dataSource[i]
执行时,因为数组已经被清空了,会报数组越界的错误。
如何解决
在多线程操作过程中,如何保护共享数据,其实已经是一个众所周知的事情了,这里总结下自己试过的处理方法:
@synchronized
NSLock
dispatch_semaphore_signal
dispatch_barrier_async
下面是使用@synchronized
修复的示例:
- (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
修复的示例:
//声明一个全局变量
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
修复的示例:
//声明全局变量
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
修复的示例:
//声明全局变量
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];
});
}