This is my first question here, so please be nice.
I struggle to understand the dynamics around a queue’s target in Swift:
-
I have read that a custom queue inherits the behaviour of its target queue.
-
In the example below,
concurrentQueuehas its attribute set to.concurrent. -
But because its target queue is
DispatchQueue.main, which is serial by definition,concurrentQueueis getting executed serially:let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent, target: DispatchQueue.main) concurrentQueue.async { for i in 1...5 { print(i) } } concurrentQueue.async { for i in 6...10 { print(i) } }Output:
1 2 3 4 5 6 7 8 9 10 -
Conversely, if I have a custom serial queue which has a global queue as its target, which is concurrent by definition, I would my custom serial queue expect to get executed concurrently.
-
However, my custom serial queue is still getting executed serially. Why is that?
PROBLEM STATEMENT:
-
Here,
serialQueuehas not attribute defined which makes it a serial queue. -
Because it has a concurrent queue
DispatchQueue.global(qos: .background)as its target, I would expect it to get executed concurrently. -
However, the output is still serial.
let serialQueue = DispatchQueue(label: "serialQueue", target: DispatchQueue.global(qos: .background)) serialQueue.async { for i in 1...5 { print(i) } } serialQueue.async { for i in 6...10 { print(i) } }Output:
1 2 3 4 5 6 7 8 9 10
>Solution :
The behavior you describe is correct.
-
If you create a private serial queue, regardless of its target, it will perform the tasks serially. The concurrent nature of the target queue does not affect the serial nature of your private queue.
-
If you create a private concurrent queue, if the target is serial, your private queue will be constrained to serial behavior of the underlying target queue.
This begs the question of what is the purpose of a target queue.
A good example would be an “exclusion queue”. See WWDC 2017 video Modernizing Grand Central Dispatch Usage. The idea is that you might have two separate serial queues, but you want them to run exclusively of each other, so you would create yet another serial queue, and use that as the target for the other two. It avoids unnecessary context switches, ensures serial behaviors across multiple queues, etc. See that video for more information.
Loosely related, see the setTarget(_:) documentation, which offers a little context for targets:
The target queue defines where blocks run, but it doesn’t change the semantics of the current queue. Blocks submitted to a serial queue still execute serially, even if the underlying target queue is concurrent. In addition, you can’t create concurrency where none exists. If a queue and its target queue are both serial, submitting blocks to both queues doesn’t cause those blocks to run concurrently. The blocks still run serially in the order the target queue receives them.