Concurrent programming with GCD in Swift 3 & # 8211; Part 1
Concurrent programming is an unfamiliar concept in the application programming world. GCD – Grand Central Dispatch is one of the ways to program multithreading. Especially in Swift 3 with a big change in syntax as well as Xcode 8 has added tools to debug thread more effectively. Through this article, IDEA will help you master the basic concurrent programming techniques that can be quickly introduced into applications that will need to be updated to the upcoming Swift 3.
Threads
Thread is the smallest unit in concurrent programming. One of the most basic features is that they help us to execute multiple tasks at the same time.
Illustrations of Threads
Grand Central Dispatch
GCD is a collection, a library to improve and simplify the use of Thread. We will no longer have to explicitly create the threads and start them, instead we will put the task (task) on the queue. These queues will manage the thread (initiating, running, and canceling the thread).
- GCD works based on Thread Pool mechanism: this is the mechanism to help optimize the creation and cancellation of threads. Imagine that in a restaurant with 10 tables, there is no need for 10 servants. Instead we just need somewhere 3-4 people.
Dispatch Queues
The queues are responsible for creating and destroying threads, executing the assigned tasks. Some characteristics of Dispatch Queues:
- There are 2 types: Serial and Concurrent .
– Serial serial queue has only 1 thread so they can only run 1 task at a time. The main queue is a special dispatch queue, available in the system and also a serial queue.
– Concurrent is a queue with more than 1 thread so it can run several tasks in parallel. The global queue is a concurrent queue and is available in the system. - The dispatch queues will execute the task in FIFO (First In First Out) order.
1 2 3 4 5 6 7 8 9 | // How to create a serial queue in Swift 3 let serialQueue = DispatchQueue (label: "mySerialQueue", attributes: [.serial]) // How to create a concurrent queue let concurrentQueue = DispatchQueue (label: "myConcurrentQueue", attributes: [.concurrent]) // The queue is available in the system let mainQueue = DispatchQueue.main let globalQueue = DispatchQueue.global () |
Dispatch Async
One of the ways to use the GCD is to use the Dispatch Async. This method is used to perform tasks asynchronously with each other (no one waits for anyone, everyone does)
1 2 3 4 5 6 7 8 9 10 11 | // Example with a serial queue let serialQueue = DispatchQueue (label: "mySerialQueue", attributes: [.serial]) for i in 1 ... 5 { serialQueue.async (execute: { print (i) sleep (1) }) } // As a serial queue, we will see them in turn print the screen from 1 to 5, separated by 1s each time. If you use a concurrent queue, these 5 tasks will execute at the same time. |
Dispatch Sync
Unlike Dispatch Async, Dispatch Sync performs tasks in a synchronized manner. Why do you need this synchronization?!? That is in the concurrent programming world, tasks have cases that share resources together. Usually they occur when tasks (threads) edit a certain variable.
1 2 3 4 5 6 7 8 9 10 11 | let concurrentQueue = DispatchQueue (label: "myConcurrentQueue", attributes: [.concurrent]) var count = 0 cho _ trong 1 ... 1000 { concurrentQueue.async (execute: { count + = 1 print (count) }) } // At the end of this round, will the last number print out if it is 1000? |
The loop on the idea will run 1000 times, each time it will increment count by 1. We have 1000 tasks running almost parallel (because we have a thread pool so it will definitely not be possible to have 1000 threads created, numbers The reality is very little, depending on the system). At the end of this round, the screen only prints somewhere 990 – 997, definitely can't be 1000 !!!
The reason is quite simple: in a certain time, there will be some threads reading and writing values for the count variable. Since it is at the same time, assuming count is 10, the thread will write to value 11 for the count variable. From there there will be times that turn count to update the old value. This problem is also known as Data Race terminology.
To fix this we just need to edit 1 line in the above code:
1 | concurrentQueue.sync (execute: {// replace async to sync, rerun the above code, we see the screen printed out 1000 |
Thread Sanitizer
This is a new tool in XCode 8, which helps us detect data race vulnerabilities in code. This is really a very useful tool because this bug is really hard to catch. How to enable Thread Sanitizer:
Deadlock
Dispatch Sync mechanically wants to synchronize data to lock the current thread, and threads in the same queue with it to perform the task need to be synchronized, after this task is executed, it will unlock threads.
In the serial queue, if we use dispatch sync to be extremely careful. Because this queue has only one thread, if we lock it to execute the dispatch sync task, then whoever will execute it because it is locked. That is deadlock, the thread is locked forever and cannot be unlocked anymore.
In addition we have another case of deadlock that if the thread A depends on thread B, but thread B depends on Thread A. Depending on this means that these two threads wait for each other to finish their task. This is the same when accidentally throwing the key into the trunk of the motorcycle and closing the trunk: we need to open the trunk to get the key but need to use the key to open the trunk !!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Dispatch sync on the main queue will result in deadlock immediately. DispatchQueue.main.sync { print ("Never be executed") } // Add 1 for deadlock serialQueue.async { print ("I need to wait ...") serialQueue.sync { print ("Never be executed") // this segment will never run } print ("Done") // this paragraph will never run } |
Dispatch After
Dispatch after is used to schedule a certain task to be executed after a certain amount of time. In Swift 3, dispatch after is easier to declare as follows:
1 2 3 | concurrentQueue.after (when: .now () + 5) { print (the "After 5s") // screen will print this line after 5s } |
DispatchWorkItem
Since Swift 3, we may not need to use a closure to create tasks, instead Apple has added a new class called DispatchWorkItem:
1 2 3 4 5 6 7 | let task = DispatchWorkItem { print ("I am DispatchWorkItem") } serialQueue.async (execute: task) concurrentQueue.async (execute: task) DispatchQueue.main.after (when: .now () + 5, execute: task) |
In summary, in Swift 3, Apple has completely changed the syntax when reporting the GCD.
Actually this is known to us before, C functions or C structure will be wrapped into objects (or struct / enum). This makes Swift 3's source code easier to report and easier to read. In the next section, IDEA will introduce you dispatch groups, quality of service and barrier in GCD.
Video of Swift Academy's GCD in Swift 3
ITZone via IDE Academy