How do I create a deadlock in Grand Central Dispatch?

喜夏-厌秋 提交于 2019-11-30 06:33:54

An intentional deadlock on a certain queue:

dispatch_queue_t queue = dispatch_queue_create("my.label", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
    dispatch_sync(queue, ^{
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    });

    // this will never be reached
}); 

It's clear here that the outer and inner blocks are operating on the same queue. Most cases where this will occur is in places where it's less obvious what queue the caller of the dispatch_sync is operating on. This usually occurs in a (deeply) nested stack where you're executing code in some class that was originally launched on a certain queue, and by accident you call a dispatch_sync to the same queue.

Simple code that creates deadlock:

dispatch_queue_t q = dispatch_queue_create("deadlock queue", DISPATCH_QUEUE_SERIAL);

NSLog(@"1");
dispatch_async(q, ^{
    NSLog(@"2");
    dispatch_sync(q, ^{
        NSLog(@"3");
    });
    NSLog(@"4");
});
NSLog(@"5");

Log output:

1
5
2

Here internal block is scheduled to be run on serial queue q but it cannot run until current block is finished, while current block, in turn, waits internal to finish as we called it synchronously.

The simplest way to block is to dispatch_sync on the current queue:

dispatch_sync(dispatch_get_current_queue(), ^{});

This blocks when the current queue is a serial queue, for example the main queue.

In latest Swift syntax:

let queue = DispatchQueue(label: "label")
queue.async {
    queue.sync {
        // outer block is waiting for this inner block to complete,
        // inner block won't start before outer block finishes
        // => deadlock
    }
    // this will never be reached
}
Bohdan Orlov

Interviewers often ask: "What is the simplest way to cause a deadlock?"

Obj-C:

dispatch_sync(dispatch_get_main_queue(), ^{});

Swift:

DispatchQueue.main.sync {}

Calling sync from the main thread will cause a deadlock because the main queue is a serial queue and sync stops current queue execution until passed block/closure has finished.

If anyone is curious, a concurrent queue does NOT deadlock if sync is called targeting the same queue. I know it's obvious but I needed to confirm only serial queues behave that way 😅

Works:

let q = DispatchQueue(label: "myQueue", attributes: .concurrent)

q.async {
    print("work async start")
    q.sync {
        print("work sync in async")
    }
    print("work async end")
}

q.sync {
    print("work sync")
}

print("done")

Fails:

Initialize q as let q = DispatchQueue(label: "myQueue") // implicitly serial queue

In Swift 4.2 you can cause a deadlock using the following piece of code:

let aSerialQueue = DispatchQueue(label: "my.label")

aSerialQueue.sync {
    // The code inside this closure will be executed synchronously.
    aSerialQueue.sync {
        // The code inside this closure should also be executed synchronously and on the same queue that is still executing the outer closure ==> It will keep waiting for it to finish ==> it will never be executed ==> Deadlock.
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!