molon molon - 6 months ago 26
iOS Question

Dead Lock With `dispatch_barrier`

With learnning

dispatch_barrier
, I wrote a example below:

static dispatch_queue_t queue;
static dispatch_queue_t readWriteLockQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.test.testasync", DISPATCH_QUEUE_CONCURRENT);
readWriteLockQueue = dispatch_queue_create("com.test.readWriteLockQueueOfMeta", DISPATCH_QUEUE_CONCURRENT);
});

for (NSInteger i=0; i<100; i++) {
dispatch_async(queue, ^{
dispatch_sync(readWriteLockQueue, ^{
NSLog(@"read");
});

dispatch_barrier_sync(readWriteLockQueue, ^{
NSLog(@"write");
});
});
}
dispatch_barrier_async(queue, ^{
NSLog(@"finished!");
});


But the output is:

2016-05-20 16:23:14.066 Test[23867:781553] read
2016-05-20 16:23:14.066 Test[23867:781555] read
2016-05-20 16:23:14.066 Test[23867:781554] read
2016-05-20 16:23:14.066 Test[23867:781545] read
2016-05-20 16:23:14.066 Test[23867:781559] read
2016-05-20 16:23:14.067 Test[23867:781564] read
2016-05-20 16:23:14.066 Test[23867:781560] read
2016-05-20 16:23:14.066 Test[23867:781561] read
2016-05-20 16:23:14.067 Test[23867:781562] read
2016-05-20 16:23:14.067 Test[23867:781565] read


No
write
appeared. It exceeded my expectations.
So I need some help about it.

dispatch_barrier_sync
or
dispatch_sync
has not call
sync
inside.
Why does it happened deadlock?

Updated:
Thanks for the answer of @originaluser2.
I updated my implement with read-write lock below to avoid virtual thread limit.

#define THREAD_ASSERT_ON_ERROR(x_) do { \
_Pragma("clang diagnostic push"); \
_Pragma("clang diagnostic ignored \"-Wunused-variable\""); \
volatile int res = (x_); \
assert(res == 0); \
_Pragma("clang diagnostic pop"); \
} while (0)

static dispatch_queue_t queue;
static dispatch_once_t onceToken;
static pthread_rwlock_t rwlock;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.test.testasync", DISPATCH_QUEUE_CONCURRENT);
THREAD_ASSERT_ON_ERROR(pthread_rwlock_init(&rwlock, NULL));
});

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

for (NSInteger i=0; i<200; i++) {
dispatch_async(queue, ^{
NSString *result = nil;
THREAD_ASSERT_ON_ERROR(pthread_rwlock_rdlock(&rwlock));
result = dict[@"test"];
THREAD_ASSERT_ON_ERROR(pthread_rwlock_unlock(&rwlock));

if (!result) {
THREAD_ASSERT_ON_ERROR(pthread_rwlock_wrlock(&rwlock));
NSLog(@"%ld:try to write",i);
result = dict[@"test"];
if (!result) {
NSLog(@"%ld:write",i);
dict[@"test"] = @"result";
result = dict[@"test"];
}
THREAD_ASSERT_ON_ERROR(pthread_rwlock_unlock(&rwlock));
}
NSLog(@"%ld:%@",i,result);
});
}

dispatch_barrier_sync(queue, ^{});
NSLog(@"completed");

Answer

The problem is that you're scheduling 100 tasks to be executed concurrently. This will exceed the virtual thread limit (usually 64) – and therefore you'll have 64 tasks sitting there waiting for their read or writes to finish, but they can't finish because there's no more threads left to do them on.

If you reduce your loop down to 64, or make your queue a serial queue in order to bottleneck the tasks, the code will once again work. Although, this is a pretty contrived example. In reality, you would never have so many contested read and writes happening concurrently (this would be an indication of a more fundamental problem in your logic) – and even if you did, your writes should most probably be happening asynchronously with a dispatch_barrier_async.