并行队列 线程安全
我的wiki地址
最近研读了SDWebImage源码。细细品味了下SDWebImageDownloaderOperation类如何利用并行queue(DISPATCH_QUEUE_CONCURRENT)实现多线程安全,兼并发。
_barrierQueue=dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue",DISPATCH_QUEUE_CONCURRENT);
我们一般都利用串行queue(DISPATCH_QUEUE_SERIAL)的FIFO特性来实现多线程安全比较典型的开源库如FMDB就是利用串行queue来实现多线程安全,在SDWebImage中同样也有这样的案例如SDImageCache中的ioQueue就是串行queue。
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
串行queue的FIFO的特性比较容易理解,而且一次只有一个线程执行,所以比较好上手,在项目实践中屡试不爽。可是利用并行queue,我们一样能实现多线程安全,并且适当条件下可以利用并行queue达到多线程并发。废话少说,不信看看SDWebImageDownloaderOperation类中的源码。在贴出代码之前首先简单说明下SDWebImageDownloaderOperation类利用并行queue(barrierQueue)来控制所有对可变数组callbackBlocks添加,删除,读取的操作是多线程安全的。在后面代码中我们可以看到所有对callbackBlocks的操作都是在并行queue(barrierQueue)中的block中执行,来确保对callbackBlocks可变数组的操作是线程安全的。
1,创建并行queue
_callbackBlocks = [NSMutableArray new];
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
2,调用dispatch_barrier_async
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
dispatch_barrier_async确保在执行[self.callbackBlocks addObject:callbacks]的时候一次只有一个线程执行,并且当执行的时候,并行queue(barrierQueue)不能执行其他block。至于为什么请参考dispatch_barrier系列API说明。后面你可以看到所有callbackBlocks的添加,删除,读取都是在barrierQueue中读取。
tips:Dispatch Barrier API说明
/*!
- @functiongroup Dispatch Barrier API
- The dispatch barrier API is a mechanism for submitting barrier blocks to a
- dispatch queue, analogous to the dispatch_async()/dispatch_sync() API.
- It enables the implementation of efficient reader/writer schemes.
- Barrier blocks only behave specially when submitted to queues created with
- the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block
- will not run until all blocks submitted to the queue earlier have completed,
- and any blocks submitted to the queue after a barrier block will not run
- until the barrier block has completed.
- When submitted to a a global queue or to a queue not created with the
- DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to
- blocks submitted with the dispatch_async()/dispatch_sync() API.
*/
3,调用dispatch_sync
- (nullable NSArray *)callbacksForKey:(NSString *)key {
__block NSMutableArray *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// We need to remove [NSNull null] because there might not always be a progress block for each callback
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
return [callbacks copy];// strip mutability here
}
这里是关键,并行queue的并发特性在SDWebImageDownloaderOperation类中就体现在dispatch_sync的调用里。在这里注意了dispatch_sync在并发queue并不是线程安全的。因为并行queue中,很有可能有多个线程同时并行执行dispatch_sync中的block。在这里需要我们理解dispatch_sync在并发queue中如何实现并发的,例如有5个线程同时执行dispatch_sync(self.barrierQueue,block),那就有可能有5个线程同时执行block,这样block就并发了(如果不是很了解dispatch_sync在并行queue如何并发可以到TestGCDQueue执行测试方法testMethod_8,就明白了)。所以在这里我们只读取self.callbackBlocks中某个key值对应的value(多线程里面的读取self.callbackBlock是线程安全的),然后mutableCopy一个实例callbacks。然后在callbacks中删除一个元素,再返回。
4,调用dispatch_barrier_sync
__block BOOL shouldCancel = NO;
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
解释和2一致,唯一区别就是dispatch_barrier_sync是同步的dispatch_barrier_async是异步的。
5,调用dispatch_barrier_async
- (void)reset {
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks removeAllObjects];
});
...
}
解释和2一致
SDWebImageDownloaderOperation类中所有和barrierQueue相关的函数就这些了。看完上面解释是否明白了
- 为何用barrierQueue是并行queue而不是串行queue?
- 你是否明白了为什么self.callbackBlocks的添加和删除都是在dispatch_barrier系列函数执行?
- 在并行queue中dispatch_sync多个线程可以同时执行?
- 为什么要读取self.callbackBlocks中key对应的值,然后mutablecopy才删除元素。
想要了解更详细代码可以下载源码SDWebImage定位到SDWebImageDownloaderOperation中,慢慢回味吧。
如果对dispatch_barrier_async,dispatch_barrier_sync,dispatch_async,dispatch_sync,并行queue,串行queue等不是很了解。可以到我的github上下载测试工程TestGCDQueue运行下。