IOS multithreading in-depth parsing
Essential concepts
Process/thread
Process: process refers to the system can run independently and as the basic unit of resource allocation, it is composed of a group of machine instructions, data and stack, is an independent operation of the active entity.
Thread: A thread is the basic execution unit of a process, in which all tasks of a process (program) are executed.
The purpose of the operating system to introduce processes: to enable multiple programs to execute concurrently, in order to improve the utilization of resources and system throughput.
The purpose of the operating system to introduce threads: in the operating system to introduce threads, it is to reduce the time and space cost paid by the concurrent execution of the program, so that the OS has better concurrency. Multithreading technology can improve the efficiency of program execution.
In an OS that introduces threads, it is common to treat processes as the basic unit of resource allocation and threads as the basic unit of independent running and independent scheduling.
Synchronous/asynchronous
Synchronization: In the case of multiple tasks, you can execute task B only after task A is complete.
Asynchronous: In the case of multiple tasks, one task A is being executed and the other task B can be executed at the same time. Task B does not wait for task A to finish. Multiple threads exist.
Parallel/concurrent
Parallelism: Two or more events occurring at the same time. Multi-core CUP opens multiple threads at the same time for the simultaneous execution of multiple tasks without interference.
Concurrency: The occurrence of two or more events at the same time interval. Context switching between one thread and another can be repeated many times, making it look as if a SINGLE CPU can and execute multiple threads. It’s actually pseudo asynchronous.
Interthread communication
In a process, threads do not exist in isolation, and multiple threads need to communicate frequently
Embodiment of communication between threads:
- One thread passes data to another thread
- After a particular task has been executed in one thread, the task continues in another thread
Multithreading concept
Multithreading refers to the concurrent execution of multiple threads on software or hardware. Generally speaking, it is in the case of synchronous or asynchronous, open up new threads, switch between threads, as well as reasonable scheduling of threads, so as to optimize and improve program performance.
Advantages of multithreading
- Can improve the execution efficiency of the program
- Appropriately improve resource utilization (CPU, memory utilization)
- Avoid blocking the main thread while processing time-consuming tasks
Disadvantages of multithreading
- Starting threads occupies a certain amount of memory space. If a large number of threads are enabled, a large amount of memory space will be occupied and the program performance will be reduced
- The more threads there are, the more overhead the CPU has on scheduling threads
- Can cause multiple threads to keep waiting on each other [deadlock]
- Programming is more complex: communication between threads, data contention between threads
GCD (Grand Central Dispatch)
Dispatch will automatically create threads to execute tasks according to the USAGE of CPU, and automatically run to multiple cores to improve the running efficiency of programs. For developers, there are no threads at the GCD level, only queues. Tasks are submitted to the queue as blocks, and the GCD automatically creates a thread pool to execute those tasks.
Advantages of GCD:
- GCD is apple’s solution to multi-core parallel computing
- – GCDS automatically utilize more CPU cores (such as dual core, quad core)
- GCD automatically manages thread lifecycles (thread creation, task scheduling, thread destruction)
- The programmer only needs to tell the COMMUNIST party what task it wants to perform, without writing any thread management code
There are two core concepts in THE COMMUNIST Party of China
Task block: What operation is to be performed
Queue: Stores tasks
There are two steps to using a COMMUNIST CD
- Customize tasks and decide what you want to do
- Add the task to the queue,
GCD
The task is automatically removed from the queue and put into the corresponding thread for execution. The fetching of tasks follows the queueFIFO
Principle: First in, first out, last in, last out.
GCD has two functions that perform tasks
-
Synchronizing tasks (sync)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); Copy the code
The synchronization task blocks the current thread, and the task in the Block is placed in the specified queue for execution. The current thread is not allowed to continue until the task in the Block is complete.
Sync is a powerful but often overlooked function. Sync allows easy synchronization between threads. One caveat, however, is that Sync is prone to deadlocks.
-
Perform tasks asynchronously
dispatch_async(dispatch_queue_t queue, dispatch_block_t block); Copy the code
An asynchronous task opens up a new thread, the current thread continues down, and the new thread executes the task in the block.
GCD queues can be divided into two main types
-
Concurrent Dispatch Queue:
- Enables concurrent execution of multiple tasks (automatically enabling multiple threads to execute tasks simultaneously)
- The parallel function is only available with asynchronous (dispatch_async) functions
If a task is placed on a parallel queue asynchronously, the GCD will FIFO the task, but the difference is that it will fetch a task on another thread, and then fetch a task on another thread. So, because I’m doing it so fast, I’m ignoring it, and it looks like all the tasks are being done together. Note, however, that GCD controls the amount of parallelism based on system resources, so if there are many tasks, it will not execute them all at once.
-
Serial Dispatch Queue:
Allow tasks to be executed one after another (after one task is completed, proceed to the next)
Synchronous execution Asynchronous execution Serial queues Current thread, executed one by one The other threads execute one by one Concurrent queue Current thread, executed one by one Open a bunch of threads, execute them together
Use Swift4 GCD
DispatchQueue
At its simplest, you can initialize a queue as follows
Debug let queue = DispatchQueue(label: "com.geselle.demoqueue ")Copy the code
The queue thus initialized is a default configured queue, which can also explicitly specify other attributes of the column
let label = "com.leo.demoQueue"
let qos = DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: nil)
Copy the code
Here, let’s analyze their role with a parameter
label
: Queue identifier to facilitate debuggingqos
: queuequality of service
. Used to indicate the “importance” of a queue, as discussed later.attributes
: Attributes of queues. Type isDispatchQueue.Attributes
Is a structure that follows the protocolOptionSet
. That means you can pass in the first argument like this[.option1,.option2]
.- Default: The queue is serial.
.concurrent
: Queues are parallel..initiallyInactive
: Indicates that queue tasks are not automatically executed and must be manually triggered by the developer.
autoreleaseFrequency
: As the name implies, automatic release frequency. Some queues are automatically released after the task is completed, and some queues are released likeTimer
Etc will not be released automatically, it needs to be released manually.
Classification of the queue
- Queue created by the system
- Main queue (corresponding to main thread)
- Global queue
- Queues created by users
Let mainQueue = dispatchqueue.main let globalQueue = dispatchqueue.global () let globalQueueWithQos = Dispatchqueue. global(qos:.userinitiated) let serialQueue = DispatchQueue(label: "Com.geselle. serialQueue") let concurrentQueue = DispatchQueue(label: "Com. Geselle. ConcurrentQueue," attributes: concurrent) / / create a parallel queue, And manually triggered the let concurrentQueue2 = DispatchQueue (label: "com. Geselle. ConcurrentQueue2", qos: . The utility, the attributes [, concurrent, initiallyInactive]) / / manual trigger if let queue = inactiveQueue {queue. Activate ()}Copy the code
suspend / resume
Suspend suspends a thread by suspending it. It hogs resources but does not run.
Resume can Resume a suspended thread and let it continue.
ConcurrentQueue. Resume () concurrentQueue. Suspend ()Copy the code
Quality of Service (QoS)
The full name of QoS is Quality of Service. In Swift 3, it is a structure that specifies the importance of a queue or task.
What is important? Prioritizing tasks when resources are limited. These priorities included CPU time, data IO, etc., but also iPad Muiti tasking (two apps running in the foreground at the same time).
Usually useQoS
Are the following four types, with lower priorities from top to bottom.
User Interactive
: Related to user interaction, such as animation, is the highest priority. For example, the calculation of continuous user dragUser Initiated
: Immediate results are needed, for examplepush
aViewController
The previous data calculationUtility
: Can take a long time to execute and then notify the user of the result. Like downloading a file and giving the user progress.Background
: Invisible to the user, such as storing large amounts of data in the background
In general, you need to ask yourself the following questions
- Is the task visible to the user?
- Is this task related to user interaction?
- What is the execution time of this task?
- Does the end result of this task have anything to do with the UI?
In GCD, there are two ways to specify QoS
Method 1: Create a queue with a specified QoS
let backgroundQueue = DispatchQueue(label: "com.geselle.backgroundQueue", qos: .background) backgroundQueue.async {// Run with QoS as background}Copy the code
Method 2: Specify the QoS when submitting blocks
Queue.async (qos:.background) {// Run with qos as background}Copy the code
DispatchGroup
DispatchGroup is used to manage the execution of a group of tasks and to listen for events when the tasks are complete. For example, multiple network requests can be made at the same time, and the UI can be reload after all the network requests are completed.
let group = DispatchGroup()
let queueBook = DispatchQueue(label: "book")
print("start networkTask task 1")
queueBook.async(group: group) {
sleep(2)
print("End networkTask task 1")
}
let queueVideo = DispatchQueue(label: "video")
print("start networkTask task 2")
queueVideo.async(group: group) {
sleep(2)
print("End networkTask task 2")
}
group.notify(queue: DispatchQueue.main) {
print("all task done")
}
Copy the code
Group. Notify The notify task will be executed after all tasks in the group (whether synchronous or asynchronous) are completed.
Group.enter / Group.leave
/* First write a function, This function takes three arguments * label for ID * cost for time * complete for callback after task completion */ public func networkTask(label:String, cost:UInt32, complete:@escaping ()->()){ print("Start network Task task%@",label) DispatchQueue.global().async { sleep(cost) Print ("End networkTask task%@",label) dispatchqueue.main.async {complete()}} created") let group = DispatchGroup() group.enter() networkTask(label: "1", cost: 2, complete: { group.leave() }) group.enter() networkTask(label: "2", cost: 2, complete: { group.leave() }) group.wait(timeout: .now() + .seconds(4)) group.notify(queue: .main, execute:{ print("All network is done") })Copy the code
Group.wait
DispatchGroup blocks the current thread and waits for execution results.
Group. Wait (timeout:.now() +.seconds(3))Copy the code
DispatchWorkItem
As mentioned above, we submit tasks as blocks (or closures). DispatchWorkItem encapsulates the task as an object.
For example, you could use it this way
Let item = DispatchWorkItem {// task} dispatchqueue.global ().async(execute: item)Copy the code
You can also specify more parameters during initialization
Let the item = DispatchWorkItem (qos: userInitiated, flags: [. EnforceQoS,. AssignCurrentContext]) {} / / task * said the first parameter to the qos. * The second parameter is of type DispatchWorkItemFlags. Specify the accessory information for the task * The third argument is the actual task blockCopy the code
The parameters for DispatchWorkItemFlags are divided into two groups
-
The implementation of
- barrier
- detached
- assignCurrentContext
-
QoS coverage information
- NoQoS // there is noQoS
- InheritQoS inherits the QoS of the Queue
- EnforceQoS // enforceQoS overwrites its Queue
After (delayed execution)
GCD can submit a deferred task via asyncAfter
Such as
Let deadline = dispatchtime.now () + 2.0 print("Start") dispatchqueue.global ().asyncafter (deadline: deadline) { print("End") }Copy the code
Delayed execution also supports a mode of DispatchWallTime
Now () + 2.0 print("Start") dispatchqueue.global ().asyncafter (wallDeadline: walltime) { print("End") }Copy the code
The difference here is
DispatchTime
Is accurate to nanosecondsDispatchWallTime
Is accurate to microseconds
Synchronization synchronous
In general, thread synchronization needs to be considered when multiple threads are simultaneously reading and writing to a variable (such as NSMutableArray). For example, if thread one addobjects NSMutableArray, thread two also wants to addObject, it has to wait until thread one has finished executing.
There are many mechanisms for achieving this synchronization
NSLock mutex
let lock = NSLock()
lock.lock()
//Do something
lock.unlock()
Copy the code
The downside of using locks is that lock and unlock have to be used in pairs, otherwise it is very easy to lock the thread and not release it.
Sync function
With GCD, there is another way of queue synchronization – sync, which synchronizes the access of attributes to a queue. This ensures thread safety when multiple threads are accessing the queue at the same time.
class MyData{
private var privateData:Int = 0
private let dataQueue = DispatchQueue(label: "com.leo.dataQueue")
var data:Int{
get{
return dataQueue.sync{ privateData }
}
set{
dataQueue.sync { privateData = newValue}
}
}
}
Copy the code
Barrier thread blocking
Suppose we have a concurrent queue that reads and writes a data object. If the operations in the queue are read, multiple operations can be performed simultaneously. If there is a write operation, you must ensure that no read operation is being performed during the write operation. You must wait until the write operation is complete before reading the data. Otherwise, incorrect data may be read. This is where the Barrier is used.
In order tobarrier flag
The submitted task is guaranteed to be the only one in a parallel queue. (Only valid for self-created queues, yesgloablQueue
Invalid)
Let’s write an example to see what happens
let concurrentQueue = DispatchQueue(label: "com.leo.concurrent", attributes: .concurrent)
concurrentQueue.async {
readDataTask(label: "1", cost: 3)
}
concurrentQueue.async {
readDataTask(label: "2", cost: 3)
}
concurrentQueue.async(flags: .barrier, execute: {
NSLog("Task from barrier 1 begin")
sleep(3)
NSLog("Task from barrier 1 end")
})
concurrentQueue.async {
readDataTask(label: "2", cost: 3)
}
Copy the code
And then, you see Log
Dispatch[15609:245546] Start data task1 2017-01-06 17:14:19.690 Dispatch[15609:245542] Start data task1 Data task2 2017-01-06 17:14:22.763 Dispatch[15609:245546] End Data Task1 2017-01-06 17:14:22.763 Dispatch[15609:245542] Dispatch[15609:245546] Task from Barrier 1 begin 2017-01-06 17:14:25.839 End Data task2 2017-01-06 17:14:22.764 Dispatch[15609:245546] Task from Barrier 1 begin 2017-01-06 17:14:25.839 Dispatch[15609:245546] Task from barrier 1 end 2017-01-06 17:14:25.839 Dispatch[15609:245546] Start data task3 Dispatch[15609:245546] End Data task3Copy the code
The effect of execution is that the barrier task commits and waits for all previous tasks to complete before executing itself. After completing the Barrier task, perform subsequent tasks.
Semaphore Semaphore
DispatchSemaphore is the encapsulation of traditional counting semaphores to control when resources are accessed by multiple tasks.
To put it simply, if I only have two USB ports, if three USB requests come in, then the third request will wait until one of them is free and then the third request will continue.
Let’s simulate this situation:
public func usbTask(label:String, cost:UInt32, complete:@escaping ()->()){
print("Start usb task%@",label)
sleep(cost)
print("End usb task%@",label)
complete()
}
let semaphore = DispatchSemaphore(value: 2)
let queue = DispatchQueue(label: "com.leo.concurrentQueue", qos: .default, attributes: .concurrent)
queue.async {
semaphore.wait()
usbTask(label: "1", cost: 2, complete: {
semaphore.signal()
})
}
queue.async {
semaphore.wait()
usbTask(label: "2", cost: 2, complete: {
semaphore.signal()
})
}
queue.async {
semaphore.wait()
usbTask(label: "3", cost: 1, complete: {
semaphore.signal()
})
}
Copy the code
log
Dispatch[5701-06 15:03:09.264] Start USB Task2 2017-01-06 15:03:09.264 Dispatch[5711-162204] Start USB task2 2017-01-06 15:03:09.264 Dispatch[5711-162204] Start USB task2 Task1 2017-01-06 15:03:11.338 Dispatch[5711:162205] End USB Task2 2017-01-06 15:03:11.338 Dispatch[5711:162204] End USB Task1 2017-01-06 15:03:12.339 Dispatch[5711:162219] Start USB task3 2017-01-06 15:03:12.411 Dispatch[5711:162219] End usb task3Copy the code
Tips: Be aware of deadlocks when using semaphores on serial Queue. If you are interested, you can change the queue to serial.
Reference article:
IOS multithreading with GCD you look at me enough
GCD Introduction (Swift 3)
IOS Multithreading – a brief introduction to various thread locks
For iOS multithreading, look at me
Multithreading for IOS development
Swift 3 Must See: Learn about GCD’s new API from usage scenarios
Analysis of the communication between threads and thread safety in iOS application development