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

  1. Customize tasks and decide what you want to do
  2. Add the task to the queue,GCDThe task is automatically removed from the queue and put into the corresponding thread for execution. The fetching of tasks follows the queueFIFOPrinciple: 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 debugging
  • qos: queuequality of service. Used to indicate the “importance” of a queue, as discussed later.
  • attributes: Attributes of queues. Type isDispatchQueue.AttributesIs 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 likeTimerEtc 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 useQoSAre 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 drag
  • User Initiated: Immediate results are needed, for examplepushaViewControllerThe previous data calculation
  • Utility: 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

  • DispatchTimeIs accurate to nanoseconds
  • DispatchWallTimeIs 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 flagThe submitted task is guaranteed to be the only one in a parallel queue. (Only valid for self-created queues, yesgloablQueueInvalid)

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