Swift icon Swift

Concurrent vs Serial Dispatch Queues

Grand Central Dispatch (GCD) dispatch queues are a powerful tool for performing tasks. Dispatch queues let you execute blocks of code either asynchronously or synchronously. You might have heard the terms serial and concurrent queues when investigating GCD. In this blog post, I will try to explain the difference.

Serial Queue

Serial queues execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread that is managed by the dispatch queue. To simply this explanation, lets look at some code:

import Foundation

let serialQueue = DispatchQueue(label: "com.queue.serial")

serialQueue.async {
    print("Task 1")
    print("1 is on main thread: \(Thread.isMainThread)")
}

serialQueue.async {
    print("Task 2")
    print("2 is on main thread: \(Thread.isMainThread)")
}

serialQueue.async {
    print("Task 3")
    print("3 is on main thread: \(Thread.isMainThread)")
}

serialQueue.async {
    print("Task 4")
    print("4 is on main thread: \(Thread.isMainThread)")
}

The queue will be serial by default if the attributes parameter is empty. In this example we only provided the label parameters. This should be a unique value so it does not impact existing queues using the same label. The code above will always output in the next order:

Graph showcasing thee Serial Queue execution time.

Task 1
1 is on main thread: false
Task 2

2 is on main thread: false
Task 3 

3 is on main thread: false
Task 4

4 is on main thread: false

We are using async here but we might as well used sync. The order would still be the same in this example. The only difference is that sync will execute on the given thread (in this case the main thread) and async will run on another thread. Try it out yourself!
Note: We can assume that a serial queue will execute one task wether we use sync or async.

Concurrent Queue

Concurrent queues execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue. To simply this explanation, lets look at some code:

import Foundation

let concurrentQueue = DispatchQueue(label: "com.queue.concurrent", attributes: .concurrent)

concurrentQueue.async {
    print("Task 1")
    print("1 is on main thread: \(Thread.isMainThread)")
}

concurrentQueue.async {
    print("Task 2")
    print("2 is on main thread: \(Thread.isMainThread)")
}

concurrentQueue.async {
    print("Task 3")
    print("3 is on main thread: \(Thread.isMainThread)")
}

concurrentQueue.async {
    print("Task 4")
    print("4 is on main thread: \(Thread.isMainThread)")
}

If we want our queue to be concurrent, we specify it using the attributes parameter. The code above will output the following:

Graph showcasing the Concurrent Queue execution time.

Task 1
Task 2
Task 3
Task 4

3 is on main thread: false
2 is on main thread: false
1 is on main thread: false
4 is on main thread: false

Like the serial queue the execution order is the same, but when a task is finished we simply do not know for sure, unlike serial. If we changed async to sync, we would get the same result as the serial queue in this example. Our lines are executed on the given thread (in this case the main thread) and by specifying sync, we basically say “wait for the task to complete before we continue”.
Note: We cannot assume the concurrent queue will only execute one task when using sync, I could have added another block using async above the sync.