Given the following snippet of code:
#import
@interface DispatchTests : XCTestCase {
dispatch_queue_t _workQueue;
dispatch_queue_t _readWriteQueue;
int _value;
}
-(void)read;
-(void)write;
@end
@implementation DispatchTests
-(void)testDispatch {
_workQueue = dispatch_queue_create("com.work", DISPATCH_QUEUE_CONCURRENT);
_readWriteQueue = dispatch_queue_create("com.readwrite", DISPATCH_QUEUE_CONCURRENT);
_value = 0;
for(int i = 0; i < 100; i++) {
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
XCTestExpectation* expectation = [self expectationWithDescription:@"dude"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:6.0 handler:nil];
}
-(void)read {
dispatch_sync(_readWriteQueue, ^{
NSLog(@"read:%d", _value);
});
}
-(void)write {
dispatch_barrier_sync(_readWriteQueue, ^{
_value++;
NSLog(@"write:%d", _value);
});
}
@end
The purpose of this test is to see if I can use a dispatch_barrier to manage a read/write lock. In this test, both the reader and writer are synchronous. The test seems to work fine when I make the barrier asynchronous, however I would like to avoid asynchronous behavior because this implementation is non-trivial.
I'm trying to understand why the write method is deadlocking. According to the GCD docs:
When the barrier block reaches the front of a private concurrent
queue, it is not executed immediately. Instead, the queue waits until
its currently executing blocks finish executing. At that point, the
queue executes the barrier block by itself. Any blocks submitted after
the barrier block are not executed until the barrier block completes.
I'm confused by what is meant by "currently executing blocks".
My interpretation is this scenario where a bunch of reads (x) get submitted, then a write (y), then more reads (z):
(x) executes
(y) waits until (x) are done
(y) blocks (z) from executing
(x) completes
(y) executes
(y) completes
(z) executes
(z) completes
解决方案
OK, after actual testing it: your code does not block - in theory.
However - in practice - it may.
What you are experiencing is a situation where all available system threads are exhausted. In order to proceed, your code would require GCD to acquire a new thread - but non is available anymore - and thus, it deadlocks.
In order to avoid such situation, you need to analyse your code where it spawns new threads in an unbound manner. This may occur with concurrent queues, where the block will block or takes too long to finish AND a high number of blocks are submitted in high frequency to that concurrent queue.
For example, if you insert a small delay:
for(int i = 0; i < 400; i++) {
usleep(1000);
dispatch_async(_workQueue, ^{
if(arc4random() % 4 == 0) {
[self write];
} else {
[self read];
}
});
}
the code may run until it finishes regularly. This of course is just to demonstrate the issue - not to fix your problem.