Final Grand Central Dispatch tutorial in Swift


GCD concurrency tutorial for freshmen

The Grand Central Dispatch (GCD, or simply Dispatch) framework relies on the underlying thread pool design sample. Which means there are a set variety of threads spawned by the system – primarily based on some components like CPU cores – they’re all the time out there ready for duties to be executed concurrently. 🚦

Creating threads on the run is an costly activity so GCD organizes duties into particular queues, and afterward the duties ready on these queues are going to be executed on a correct and out there thread from the pool. This method results in nice efficiency and low execution latency. We are able to say that the Dispatch framework is a really quick and environment friendly concurrency framework designed for contemporary multi-core {hardware} and desires.

Concurrency, multi-tasking, CPU cores, parallelism and threads

A processor can run duties made by you programmatically, that is normally referred to as coding, creating or programming. The code executed by a CPU core is a thread. So your app goes to create a course of that’s made up from threads. 🤓

Up to now a processor had one single core, it may solely take care of one activity at a time. In a while time-slicing was launched, so CPU’s may execute threads concurrently utilizing context switching. As time handed by processors gained extra horse energy and cores in order that they have been able to actual multi-tasking utilizing parallelism. ⏱

These days a CPU is a really highly effective unit, it is able to executing billions of duties (cycles) per second. Due to this excessive availability velocity Intel launched a expertise referred to as hyper-threading. They divided CPU clock cycles between (normally two) processes working on the similar time, so the variety of out there threads primarily doubled. 📈

As you’ll be able to see concurrent execution could be achieved with varied methods, however you needn’t care about that a lot. It is as much as the CPU structure the way it solves concurrency, and it is the working system’s activity how a lot thread goes to be spawned for the underlying thread pool. The GCD framework will cover all of the complexity, but it surely’s all the time good to know the essential rules. 👍

Synchronous and asynchronous execution

Every work merchandise could be executed both synchronously or asynchronously.

Have you ever ever heard of blocking and non-blocking code? This is similar scenario right here. With synchronous duties you may block the execution queue, however with async duties your name will immediately return and the queue can proceed the execution of the remaining duties (or work objects as Apple calls them). 🚧

Synchronous execution

When a piece merchandise is executed synchronously with the sync technique, this system waits till execution finishes earlier than the tactic name returns.

Your operate is most certainly synchronous if it has a return worth, so func load() -> String goes to in all probability block the factor that runs on till the sources is totally loaded and returned again.

Asynchronous execution

When a piece merchandise is executed asynchronously with the async technique, the tactic name returns instantly.

Completion blocks are sing of async strategies, for instance should you take a look at this technique func load(completion: (String) -> Void) you’ll be able to see that it has no return kind, however the results of the operate is handed again to the caller afterward by a block.

This can be a typical use case, if you need to look ahead to one thing inside your technique like studying the contents of an enormous file from the disk, you do not wish to block your CPU, simply due to the sluggish IO operation. There could be different duties that aren’t IO heavy in any respect (math operations, and so on.) these could be executed whereas the system is studying your file from the bodily arduous drive. 💾

With dispatch queues you’ll be able to execute your code synchronously or asynchronously. With synchronous execution the queue waits for the work, with async execution the code returns instantly with out ready for the duty to finish. ⚡️

Dispatch queues

As I discussed earlier than, GCD organizes activity into queues, these are similar to the queues on the shopping center. On each dispatch queue, duties might be executed in the identical order as you add them to the queue – FIFO: the primary activity within the line might be executed first – however it is best to be aware that the order of completion isn’t assured. Duties might be accomplished in response to the code complexity. So should you add two duties to the queue, a sluggish one first and a quick one later, the quick one can end earlier than the slower one. ⌛️

Serial and concurrent queues

There are two varieties of dispatch queues. Serial queues can execute one activity at a time, these queues could be utilized to synchronize entry to a selected useful resource. Concurrent queues alternatively can execute a number of duties parallel in the identical time. Serial queue is rather like one line within the mall with one cashier, concurrent queue is like one single line that splits for 2 or extra cashiers. 💰

Foremost, world and customized queues

The principle queue is a serial one, each activity on the primary queue runs on the primary thread.

World queues are system offered concurrent queues shared by the working system. There are precisely 4 of them organized by excessive, default, low precedence plus an IO throttled background queue.

Customized queues could be created by the consumer. Customized concurrent queues all the time mapped into one of many world queues by specifying a High quality of Service property (QoS). In a lot of the instances if you wish to run duties in parallel it is suggested to make use of one of many world concurrent queues, it is best to solely create customized serial queues.

System offered queues

  • Serial foremost queue
  • Concurrent world queues
  • excessive precedence world queue
  • default precedence world queue
  • low precedence world queue
  • world background queue (IO throttled)

Customized queues by high quality of service

  • userInteractive (UI updates) -> serial foremost queue
  • userInitiated (async UI associated duties) -> excessive precedence world queue
  • default -> default precedence world queue
  • utility -> low precedence world queue
  • background -> world background queue
  • unspecified (lowest) -> low precedence world queue

Sufficient from the idea, let’s have a look at methods to use the Dispatch framework in motion! 🎬

The way to use the DispatchQueue class in Swift?

Right here is how one can get all of the queues from above utilizing the model new GCD syntax out there from Swift 3. Please be aware that it is best to all the time use a worldwide concurrent queue as an alternative of making your individual one, besides if you’ll use the concurrent queue for locking with limitations to attain thread security, extra on that later. 😳

The way to get a queue?

import Dispatch

DispatchQueue.foremost
DispatchQueue.world(qos: .userInitiated)
DispatchQueue.world(qos: .userInteractive)
DispatchQueue.world(qos: .background)
DispatchQueue.world(qos: .default)
DispatchQueue.world(qos: .utility)
DispatchQueue.world(qos: .unspecified)

DispatchQueue(
    label: "com.theswiftdev.queues.serial"
)

DispatchQueue(
    label: "com.theswiftdev.queues.concurrent", 
    attributes: .concurrent
)

So executing a activity on a background queue and updating the UI on the primary queue after the duty completed is a fairly simple one utilizing Dispatch queues.

DispatchQueue.world(qos: .background).async {
    

    DispatchQueue.foremost.async {
        
    }
}

Sync and async calls on queues

There isn’t a huge distinction between sync and async strategies on a queue. Sync is simply an async name with a semaphore (defined later) that waits for the return worth. A sync name will block, alternatively an async name will instantly return. 🎉

let q = DispatchQueue.world()

let textual content = q.sync {
    return "this may block"
}
print(textual content)

q.async {
    print("this may return immediately")
}

Principally should you want a return worth use sync, however in each different case simply go together with async. DEADLOCK WARNING: it is best to by no means name sync on the primary queue, as a result of it will trigger a impasse and a crash. You need to use this snippet in case you are on the lookout for a secure option to do sync calls on the primary queue / thread. 👌

Do not name sync on a serial queue from the serial queue’s thread!

Delay execution

You’ll be able to merely delay code execution utilizing the Dispatch framework.

DispatchQueue.foremost.asyncAfter(deadline: .now() + .seconds(2)) {
    
}

Carry out concurrent loop

Dispatch queue merely means that you can carry out iterations concurrently.

DispatchQueue.concurrentPerform(iterations: 5) { (i) in
    print(i)
}

Debugging

Oh, by the best way it is only for debugging goal, however you’ll be able to return the identify of the present queue through the use of this little extension. Don’t use in manufacturing code!!!

extension DispatchQueue {
    static var currentLabel: String {
        .init(validatingUTF8: __dispatch_queue_get_label(nil))!
    }
}

Utilizing DispatchWorkItem in Swift

DispatchWorkItem encapsulates work that may be carried out. A piece merchandise could be dispatched onto a DispatchQueue and inside a DispatchGroup. A DispatchWorkItem can be set as a DispatchSource occasion, registration, or cancel handler.

So that you similar to with operations through the use of a piece merchandise you’ll be able to cancel a working activity. Additionally work objects can notify a queue when their activity is accomplished.

var workItem: DispatchWorkItem?
workItem = DispatchWorkItem {
    for i in 1..<6 {
        guard let merchandise = workItem, !merchandise.isCancelled else {
            print("cancelled")
            break
        }
        sleep(1)
        print(String(i))
    }
}

workItem?.notify(queue: .foremost) {
    print("executed")
}


DispatchQueue.world().asyncAfter(
    deadline: .now() + .seconds(2)
) {
    workItem?.cancel()
}
DispatchQueue.foremost.async(execute: workItem!)

Concurrent duties with DispatchGroups

So you want to carry out a number of community calls as a way to assemble the information required by a view controller? That is the place DispatchGroup might help you. All your lengthy working background activity could be executed concurrently, when all the pieces is prepared you may obtain a notification. Simply watch out you need to use thread-safe knowledge constructions, so all the time modify arrays for instance on the identical thread! 😅

func load(delay: UInt32, completion: () -> Void) {
    sleep(delay)
    completion()
}

let group = DispatchGroup()

group.enter()
load(delay: 1) {
    print("1")
    group.go away()
}

group.enter()
load(delay: 2) {
    print("2")
    group.go away()
}

group.enter()
load(delay: 3) {
    print("3")
    group.go away()
}

group.notify(queue: .foremost) {
    print("executed")
}

Notice that you just all the time must steadiness out the enter and go away calls on the group. The dispatch group additionally permits us to trace the completion of various work objects, even when they run on completely different queues.

let group = DispatchGroup()
let queue = DispatchQueue(
    label: "com.theswiftdev.queues.serial"
)
let workItem = DispatchWorkItem {
    print("begin")
    sleep(1)
    print("finish")
}

queue.async(group: group) {
    print("group begin")
    sleep(2)
    print("group finish")
}
DispatchQueue.world().async(
    group: group, 
    execute: workItem
)



group.notify(queue: .foremost) {
    print("executed")
}

Another factor that you need to use dispatch teams for: think about that you just’re displaying a properly animated loading indicator when you do some precise work. It’d occurs that the work is completed sooner than you’d count on and the indicator animation couldn’t end. To unravel this example you’ll be able to add a small delay activity so the group will wait till each of the duties end. 😎

let queue = DispatchQueue.world()
let group = DispatchGroup()
let n = 9
for i in 0..<n {
    queue.async(group: group) {
        print("(i): Working async activity...")
        sleep(3)
        print("(i): Async activity accomplished")
    }
}
group.wait()
print("executed")

Semaphores

A semaphore is just a variable used to deal with useful resource sharing in a concurrent system. It is a actually highly effective object, listed here are a couple of vital examples in Swift.

The way to make an async activity to synchronous?

The reply is easy, you need to use a semaphore (bonus level for timeouts)!

enum DispatchError: Error {
    case timeout
}

func asyncMethod(completion: (String) -> Void) {
    sleep(2)
    completion("executed")
}

func syncMethod() throws -> String {

    let semaphore = DispatchSemaphore(worth: 0)
    let queue = DispatchQueue.world()

    var response: String?
    queue.async {
        asyncMethod { r in
            response = r
            semaphore.sign()
        }
    }
    semaphore.wait(timeout: .now() + 5)
    guard let end result = response else {
        throw DispatchError.timeout
    }
    return end result
}

let response = strive? syncMethod()
print(response)

Lock / single entry to a useful resource

If you wish to keep away from race situation you might be in all probability going to make use of mutual exclusion. This might be achieved utilizing a semaphore object, but when your object wants heavy studying functionality it is best to contemplate a dispatch barrier primarily based answer. 😜

class LockedNumbers {

    let semaphore = DispatchSemaphore(worth: 1)
    var components: [Int] = []

    func append(_ num: Int) {
        self.semaphore.wait(timeout: DispatchTime.distantFuture)
        print("appended: (num)")
        self.components.append(num)
        self.semaphore.sign()
    }

    func removeLast() {
        self.semaphore.wait(timeout: DispatchTime.distantFuture)
        defer {
            self.semaphore.sign()
        }
        guard !self.components.isEmpty else {
            return
        }
        let num = self.components.removeLast()
        print("eliminated: (num)")
    }
}

let objects = LockedNumbers()
objects.append(1)
objects.append(2)
objects.append(5)
objects.append(3)
objects.removeLast()
objects.removeLast()
objects.append(3)
print(objects.components)

Watch for a number of duties to finish

Similar to with dispatch teams, you may also use a semaphore object to get notified if a number of duties are completed. You simply have to attend for it…

let semaphore = DispatchSemaphore(worth: 0)
let queue = DispatchQueue.world()
let n = 9
for i in 0..<n {
    queue.async {
        print("run (i)")
        sleep(3)
        semaphore.sign()
    }
}
print("wait")
for i in 0..<n {
    semaphore.wait()
    print("accomplished (i)")
}
print("executed")

Batch execution utilizing a semaphore

You’ll be able to create a thread pool like habits to simulate restricted sources utilizing a dispatch semaphore. So for instance if you wish to obtain plenty of pictures from a server you’ll be able to run a batch of x each time. Fairly helpful. 🖐

print("begin")
let sem = DispatchSemaphore(worth: 5)
for i in 0..<10 {
    DispatchQueue.world().async {
        sem.wait()
        sleep(2)
        print(i)
        sem.sign()
    }
}
print("finish")

The DispatchSource object

A dispatch supply is a basic knowledge kind that coordinates the processing of particular low-level system occasions.

Indicators, descriptors, processes, ports, timers and lots of extra. The whole lot is dealt with by the dispatch supply object. I actually do not wish to get into the main points, it is fairly low-level stuff. You’ll be able to monitor recordsdata, ports, indicators with dispatch sources. Please simply learn the official Apple docs. 📄

I might prefer to make just one instance right here utilizing a dispatch supply timer.

let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now(), repeating: .seconds(1))
timer.setEventHandler {
    print("hi there")
}
timer.resume()

Thread-safety utilizing the dispatch framework

Thread security is an inevitable matter if it involves multi-threaded code. At first I discussed that there’s a thread pool underneath the hood of GCD. Each thread has a run loop object related to it, you’ll be able to even run them by hand. For those who create a thread manually a run loop might be added to that thread robotically.

let t = Thread {
    print(Thread.present.identify ?? "")
     let timer = Timer(timeInterval: 1, repeats: true) { t in
         print("tick")
     }
     RunLoop.present.add(timer, forMode: .defaultRunLoopMode)

    RunLoop.present.run()
    RunLoop.present.run(mode: .commonModes, earlier than: Date.distantPast)
}
t.identify = "my-thread"
t.begin()

You shouldn’t do that, demo functions solely, all the time use GCD queues!

Queue != Thread

A GCD queue isn’t a thread, should you run a number of async operations on a concurrent queue your code can run on any out there thread that matches the wants.

Thread security is all about avoiding tousled variable states

Think about a mutable array in Swift. It may be modified from any thread. That is not good, as a result of ultimately the values within it are going to be tousled like hell if the array isn’t thread secure. For instance a number of threads try to insert values to the array. What occurs? In the event that they run in parallel which ingredient goes to be added first? Now for this reason you want generally to create thread secure sources.

Serial queues

You need to use a serial queue to implement mutual exclusivity. All of the duties on the queue will run serially (in a FIFO order), just one course of runs at a time and duties have to attend for one another. One huge draw back of the answer is velocity. 🐌

let q = DispatchQueue(label: "com.theswiftdev.queues.serial")

q.async() {
  
}

q.sync() {
  
}

Concurrent queues utilizing limitations

You’ll be able to ship a barrier activity to a queue should you present an additional flag to the async technique. If a activity like this arrives to the queue it will be certain that nothing else might be executed till the barrier activity have completed. To sum this up, barrier duties are sync (factors) duties for concurrent queues. Use async limitations for writes, sync blocks for reads. 😎

let q = DispatchQueue(label: "com.theswiftdev.queues.concurrent", attributes: .concurrent)

q.async(flags: .barrier) {
  
}

q.sync() {
  
}

This technique will lead to extraordinarily quick reads in a thread secure atmosphere. You can too use serial queues, semaphores, locks all of it depends upon your present scenario, but it surely’s good to know all of the out there choices is not it? 🤐

A number of anti-patterns

It’s important to be very cautious with deadlocks, race circumstances and the readers writers downside. Normally calling the sync technique on a serial queue will trigger you a lot of the troubles. One other challenge is thread security, however we have already coated that half. 😉

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

queue.sync {
    
    queue.sync {
        
    }
}


DispatchQueue.world(qos: .utility).sync {
    
    DispatchQueue.foremost.sync {
        
    }
}

The Dispatch framework (aka. GCD) is a tremendous one, it has such a possible and it actually takes a while to grasp it. The true query is that what path goes to take Apple as a way to embrace concurrent programming into a complete new stage? Guarantees or async / await, perhaps one thing completely new, let’s hope that we’ll see one thing in Swift 6.

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox