为什么在自定义并发队列上死锁dispatch_sync
在自定义并发dispatch_queue上使用dispatch_sync时,我在应用程序中看到间歇性死锁。 我正在使用类似于Mike Ash博客中描述的方法来支持并发读取访问,但支持NSMutableDictionary上的线程安全突变,充当当前活动networkingRPC请求的caching。 我的项目使用ARC。
我创build队列:
dispatch_queue_t activeRequestsQueue = dispatch_queue_create("my.queue.name", DISPATCH_QUEUE_CONCURRENT);
和可变的字典
NSMutableDictionary *activeRequests = [[NSMutable dictionary alloc] init];
我从队列中读取这样的元素:
- (id)activeRequestForRpc: (RpcRequest *)rpc { assert(![NSThread isMainThread]); NSString * key = [rpc getKey]; __block id obj = nil; dispatch_sync(activeRequestsQueue, ^{ obj = [activeRequests objectForKey: key]; }); return obj; }
我从caching中添加和删除rpcs
- (void)addActiveRequest: (RpcRequest *)rpc { NSString * key = [rpc getKey]; dispatch_barrier_async(activeRequestsQueue, ^{ [activeRequests setObject: rpc forKey: key]; }); } - (void)removeActiveRequest: (RpcRequest *)rpc { NSString * key = [rpc getKey]; dispatch_barrier_async(activeRequestsQueue, ^{ [activeRequests removeObjectForKey:key]; }); }
我看到在调用activeRequestForRpc时,我一次发出大量的networking请求,这导致我相信其中一个屏障块(添加或删除)没有完成执行死锁。 我总是从后台线程调用activeRequestForRpc,并且应用程序UI不冻结,所以我不认为它必须阻塞主线程,但我添加了assert语句以防万一。 关于这个僵局如何发生的任何想法?
更新:添加调用这些方法的代码
我正在使用AFNetworking进行networking请求,我有一个NSOperationQueue,我正在调度'检查caching,也许从networking获取资源'的逻辑。 我将调用CheckCacheAndFetchFromNetworkOp。 在那里,我打电话给我的AFHTTPClient的自定义子类发出RPC请求。
// this is called from inside an NSOperation executing on an NSOperationQueue. - (void) enqueueOperation: (MY_AFHTTPRequestOperation *) op { NSError *error = nil; if ([self activeRequestForRpc:op.netRequest.rpcRequest]) { error = [NSError errorWithDomain:kHttpRpcErrorDomain code:HttpRpcErrorDuplicate userInfo:nil]; } // set the error on the op and cancels it so dependent ops can continue. [op setHttpRpcError:error]; // Maybe enqueue the op if (!error) { [self addActiveRequest:op.netRequest.rpcRequest]; [self enqueueHTTPRequestOperation:op]; } }
MY_AFHTTRequestOperation由AFHTTPClient实例构build,并在成功和失败完成块内部调用[self removeActiveRequest:netRequest.rpcRequest];
作为第一个行动。 这些块通过AFNetworking在主线程上执行作为默认行为。
我已经看到死锁发生在必须持有队列上的锁的最后一个障碍块同时是add块和remove块。
是否有可能,因为系统产生更多的线程来支持我的NSOperationQueue中的CheckCacheAndFetchFromNetworkOp Ops,activeRequestsQueue的优先级太低,无法进行调度? 如果所有线程都被CheckCacheAndFetchFromNetworkOps阻塞尝试从activeRequests字典中读取,并且activeRequestsQueue在无法执行的添加/删除障碍块上阻塞,则可能会导致死锁。
UPDATE
通过将NSOperationQueue设置为maxConcurrentOperation计数为1(或者除了默认的NSOperationQueueDefaultMaxConcurrentOperationCount之外的任何合理的值)来解决这个问题。
基本上我拿走的教训是,你不应该有一个NSOperationQueue默认的最大操作数等待任何其他dispatch_queue_t或NSOperationQueue,因为它可能潜在所有线程从其他队列。
这是发生了什么事。
队列 – NSOperationQueue设置为默认NSDefaultMaxOperationCount,它允许系统确定要运行的并发操作数。
op – 在queue1上运行,并在读取后在AFNetworking队列上调度networking请求,以确保RPC不在activeRequest集合中。
这是stream程:
系统确定它可以支持10个并发线程(实际上它更像80)。
10个ops被安排一次。 系统允许10个操作同时在10个线程上运行。 所有10个操作都调用hasActiveRequestForRPC,它在activeRequestQueue上调度同步块并阻塞10个线程。 activeRequestQueue想要运行它的读取块,但没有任何可用的线程。 此时我们已经陷入僵局。
更常见的是,我会看到类似于9个操作(1-9)的计划,其中一个操作(op1)在第10个线程上快速运行hasActiveRequestForRPC并计划addActiveRequest barrer块。 然后另一个操作将在第10个线程上计划,op2-10将安排并等待hasActiveRequestForRPC。 然后,op1的预定addRpc块将不会运行,因为op10占用了最后一个可用线程,而另一个hasActiveRequestForRpc块将等待屏障块执行。 当op1尝试在不能访问任何线程的不同操作队列上调度caching操作时,op1将最终阻塞。
我假定阻塞hasActiveRequestForRPC正在等待barrer块执行,但关键是activeRequestQueue等待任何线程可用性。
编辑:原来的问题是,调用enqueueOperation:
使用所有可用的线程,因为他们都等待(通过dispatch_sync)在activeRequestsQueue
发生的activeRequestsQueue
。 减less这个队列上的maxConcurrentOperations解决了这个问题(见注释),尽pipe这不是一个很好的解决scheme,因为它假设了核心的数量等等。更好的解决scheme是使用dispatch_async
而不是dispatch_sync
,虽然这会使代码更复杂。
我早先的build议:
-
当你已经在activeRequestsQueue上时
dispatch_sync(activeRequestsQueue, ...)
你正在调用dispatch_sync(activeRequestsQueue, ...)
(你的断言由于某种原因没有开启,就像你在release中运行一样)。 -
[activeRequests removeObjectForKey:key];
正在导致请求被释放,并且dealloc正在等待一些调用activeRequestForRpc:
东西,这会导致死锁。