I thought calling a concurrent queue synchronously from within itself must lead to a deadlock, but it did not happened. I asked a GPT why the deadlock did not occur; however, GPT (4.5) believes this code causes a dead lock.
The thought process behind my and GPT’s logic is as follows:
q.async { ... } starts a task on queue q.
That block tries to run q.sync { ... }.
But q.sync tries to run immediately on queue q, which is already busy running the outer async block.
So:
Outer block waits for inner sync to complete.
Inner sync waits for queue to be free.
Stuck forever → deadlock.
Note: Interestingly enough, q executes all code on the same thread according to Thread.current property
When you created the queue, you specified it as “concurrent”, which means it allows more than one block to be active at a time. Custom concurrent queues are only really useful in a few niche circumstances (distinct from the global background queues). I don’t think I’ve ever needed one myself.
I use this block of code just for my personal understanding, it dose not have any particular reason to exist in real life. I thought that .sync method blocks a thread(of a queue); since all Thread.current were the same inside both nested and outer blocks I thought it could lead to a deadlock. Now, I get it thanks
It’s documented to deadlock, although when I’m calling “sync” targeting the same queue I am currently on – the most logical outcome would be calling the thing synchronously without extra blocking. Pseudocode:
That would give you the isolation properties of a recursive lock, which is “basically none”. I know “make sure this happens on the main queue” is sometimes the only important part, but otherwise you’re getting await’s “might suspend here” caveat for every call you make within the queue, only unmarked. I’m not saying GCD’s model is perfect, but it is a safer default.
It is not a deadlock. It will deadlock if you do this with a serial queue, but not with a concurrent queue.
Yes, that can happen. But if you challenge ChatGPT’s answer with “but, wait, this is a concurrent queue, so it can use a different worker thread, avoiding the deadlock”, ChatGPT will change its answer and say it won’t deadlock.
Not infrequently, ChatGPT will provide an answer that is incorrect, but as soon as you challenge it, the LLM will often subsequently generate another answer that contradicts its original one. This happens with these LLMs. Annoyingly, both of the contradictory answers are often offered with the same confident tone, leaving it up to us to discover the truth.
Anyway, going back to the code snippet, the conceptual process is as follows:
Thequeue.async {…} starts a task on a worker thread A for queue q;
That worker thread tries to run q.sync {…}, blocking worker thread A until the sync is done;
While that worker thread is blocked, q.sync {…} will get the next available worker thread, B, for queue q;
That second worker thread B for the inner block prints “Hi”;
The inner (sync) closure finishes and worker thread B is released back to the worker thread pool; and
The outer (async) closure will then also finish, yielding worker thread A back to the work thread pool.
Actually, it is more complicated than that. At step 3, as a clever optimization, the inner sync closure may actually run on the caller’s thread, A, eliminating the need to introduce a separate worker thread. (GCD is smart enough to know that if you dispatch synchronously, that the calling thread is blocked anyway, so it can actually avail itself of the caller’s thread for the sync code, saving an expensive thread hop.) But, even with this optimization, the concurrent queue will not deadlock.
In short, you describe a process that is a classic serial queue deadlock, but not a deadlock on a concurrent queue. And if you try this code, you can verify that this indeed does not deadlock (when using a concurrent queue).
Just to clarify, you said:
No, queue q is not “busy” (or, more accurately, it is not blocked). Its first worker thread may be blocked for the duration of the sync call (my aforementioned optimization notwithstanding), but the queue, itself, is free to run other items on other worker threads. As a concurrent queue, it is free to do other work on other threads while one of its threads are busy/blocked. This is what differentiates a concurrent queue from a serial queue.