Multithreading is an important link in iOS development, no matter in daily development, or in the interview, has a pivotal position, so I plan to open a small topic to study the underlying principles of multithreading. This chapter is the first to introduce some of the basics of conceptual multithreading.

process

define

When a program runs in memory, it becomes a process. Process is in the running process of the program, and has a certain independent function, process is an independent unit of the system for resource allocation and scheduling. Each process is independent of each other, and each process runs in its own dedicated and protected memory.

In iOS development, an App is a process in memory, independent of each other and can only access its own sandbox controls, which is one of the main reasons why Apple runs smoothly and safely.

The characteristics of

  • Independence: an entity that exists independently in the system and has its own resources and private address space. A user’s process cannot directly access the address space of another process without permission from the process itself.
  • Dynamic: the difference between process and program is that the program is only a static instruction set, while the process is an instruction set that is active in the system, and the concept of time is added to the process. Processes have their own life cycle and different states that programs don’t.
  • Concurrency: Multiple processes can execute concurrently on a single processor without affecting each other.

thread

define

The definition of thread can be summed up in the following three points:

  1. A thread is the basic execution unit of a process, in which all tasks of a process are executed
  2. In order for a process to perform a task, it must have threads, at least one thread
  3. By default, the program starts a thread, which is called the main thread or UI thread

The relationship between processes and threads

  • Address space: Threads of the same process share the address space of the same process, while processes are independent of each other.

  • Resource ownership: Threads in the same process share the resources of the same process, such as memory, I/O, and CPU, but resources between processes are independent.

  • The crash of one process in protected mode does not affect other processes, but the crash of one thread kills the entire process. So multi-processing is more robust than multi-threading.

  • Process switchover consumes large resources and is efficient. So when it comes to frequent switching, threads are better than processes. Also, if you require concurrent operations that share variables at the same time, use threads instead of processes

  • Execution process: Each independent process has a program run entry, sequential execution sequence, and program entry. However, the thread cannot be executed independently and must be dependent on the application, which provides multiple thread execution control.

  • Threads are the basic unit of processor scheduling, but processes are not.

The relationship between threads and runloops

  1. runloopAnd the thread isOne to one correspondence, arunloopIt corresponds to a core thread, why is it core, becauserunloopCan be nested, but there can only be one core, and their relationships are stored in a global dictionary.
  2. runloopIs to manage threads, when threadsrunloopWhen enabled, the thread enters after completing the taskA dormant state“, and will be awakened to perform the task.
  3. runloopIs created on the first fetch and destroyed at the end of the thread.
  4. For the main thread,runloopIt is created by default as soon as the program starts.
  5. For child threads,runloopIt is lazily loaded and is created only when we use it, so beware of using timers in child threads:Ensure that the child threadrunloopBe createdOtherwise, the timer will not callback

The queue

define

Queues, also known as queues, are linear FIFO (first-in-first-out) lists, which are implemented by linked lists or arrays In specific applications. Load the queue structure of threaded tasks. Queues only allow insertion at the back end (called rear) and deletion at the front (called front). Queues operate like stacks, except that queues allow only new data to be added at the back end.

type

The type of queue determines the execution mode of the task (concurrent or serial), including the following types:

  • Concurrent queue (Concurrent Dispatch Queue) : Thread execution can be executed simultaneously, and the next one can be executed without the previous one finishing.
  • Serial queue (Serial Dispatch Queue) : Thread execution can only be executed one by one, waiting for the previous execution to finish before executing the next one.
  • Main queue: Bound to the main thread, where all tasks are executed, with specially processed serial queues.
  • Global queue: concurrent queue provided by the system.

Synchronous and asynchronous

Sync: Only tasks can be executed on the current thread in sequence and new threads cannot be started.

Asynchronous Async: Performs tasks in a new thread and has the ability to start a new thread.

multithreading

Concepts and Principles

A process can be concurrent with multiple threads to perform their respective tasks at the same time, called multithreading.

Time-sharing operating system will reduce the CPU time length is divided into basic same time interval, called time slice, within a time slice, the CPU can only handle one thread of a task, for a single core CPU, in different time to perform the tasks in different threads, to form the multiple tasks at the same time carry out the “illusion”.

Multithreading is the rapid switching between threads in a unit time slice

The life cycle

  • New: Creates a thread
  • Ready (Runnable) :startafterrunnableAnd waitcpuAllocate resources and make calls
  • Running: When obtainedcpuIt’ll be there as soon as it’s dispatchedrunningstate
  • Block: when a task in a thread is abnormal, for examplesleep, or a deadlock, which causes the thread to block. It doesn’t come directly when the block is unblockedrunningIt’s going to be ready again
  • Dead: The task is completed or forcibly exits

The advantages and disadvantages

Advantages:

  1. Can improve the execution efficiency of the program
  2. Appropriately improve resource utilization (CPU, memory)
  3. The task on the thread is automatically destroyed after it completes execution

Disadvantages:

  1. Starting threads takes up a certain amount of memory (512KB per thread by default)
  2. If you start a large number of threads, it will occupy a large amount of memory space, reducing the performance of the program
  3. The more threads there are, the more overhead the CPU has on calling threads
  4. The program design is more complicated, such as communication between threads, multi-thread data sharing

Multithreading in iOS

  1. pthread: that is,POSIX Thread, abbreviated aspthread, is threadPOSIXStandard is a set of universal multithreadingAPI, can be inUnix/Linux/WindowsSuch as cross-platform use. It’s basically not used in iOS.
  2. NSThread: Apple encapsulates the object-oriented thread class, can directly manipulate the thread, compared toGCD.NSThreadMore efficient, created by the programmer, when the task in the thread is finished, the thread will automatically exit, programmers can also manually manage the life cycle of the thread. Use frequency is low.
  3. GCDFull name:Grand Central DispatchC, Apple’s solution to multi-core parallel computing,CGDWill automatically utilize moreCPUThe kernel, which automatically manages the thread lifecycle, the programmer just needs to tellGCDTasks that need to be performed without writing any code to manage threads.GCDAlso for iOSThe highest frequencyMultithreading technology.
  4. NSOperationBased on:GCDEncapsulation of object-oriented multithreading technology, often withNSOperationQueueUse, high frequency of use.

GCD is different from NSOperation

  1. GCDSupport onlyFIFOQueues, which do not support dependency Settings between asynchronous operations. whileNSOperationThe queue can be reprioritized to adjust the execution order of different operations.
  2. NSOperationsupportKVOTo observe the task execution status.
  3. GCDCloser to the bottom,GCDIt is fastest for low-level operations that seek performance.
  4. From transactionality between asynchronous operations, sequential rows, dependencies.GCDYou have to write more code to do that, andNSOperationThis support is already built in
  5. If the process of asynchronous operations requires more interaction and UI presentation,NSOperationFor the better. In the underlying code, tasks are less interdependent and require more concurrency,GCDIs more advantageous

Summary chart

The thread pool

define

A thread pool is a form of multithreaded processing in which tasks are added to a queue and then automatically started after a thread is created. The threads in the thread pool are background threads. Each thread has a default stack size, runs at a default priority, and is in a multithreaded cell.

Execute the process

  1. Check whether the thread pool size is smaller than the core thread pool size
  2. If less than. Create a thread to execute the task
  3. If the work queue is not full, submit the task to the work queue and create thread to execute the task. If the work queue is full, determine whether all the threads are working. If they are working to the saturation policy, if not full create thread to execute the task

Saturated strategy

  1. AbortPolicy: Default policy. Direct sellingRejectedExecutionExeceptionAn exception prevents the system from working properly and is caught by the caller
  2. CallerRunsPolicy: Regulation mechanism. Neither discard nor report exceptions. Roll back the task to the caller
  3. DisOldestPolicy: Drop the most waiting task
  4. DisCardPolicy: Directly discards the task

Interthread communication

Apple’s official documentation provides several ways to communicate between threads:

  • Direct messagingThrough:performSelectorA set of methods for executing tasks specified by one thread on another. Since the execution context of the task is the target thread, messages sent in this manner will be automatically serialized.
  • Global variables, shared memory blocks, and objects: Another simple way to pass information between two threads is to use global variables, shared objects, or shared memory blocks. Although shared variables are fast and easy, they are more fragile than direct messaging. Shared variables must be carefully protected using locks or other synchronization mechanisms to ensure correct code. Failure to do so may result in competitive conditions, data corruption, or crashes.
  • Conditional execution: A condition is a synchronization tool that can be used to control when a thread executes a particular part of code. You can treat a condition as a lock and let the thread run only when the specified condition is met.
  • Runloop sources: a customRunloop sourceConfiguration allows specific application messages to be received on a thread. Due to theRunloop sourceIs event-driven, so threads automatically go to sleep when there is nothing to do, increasing thread efficiency.
  • Ports and socketsBased on:portCommunication is a more sophisticated way of communicating between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. To improve efficiency, useRunloop sourceSo when there is no data waiting on the port, the thread will go to sleep.
  • Message queues: Traditional multiprocessing services define a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communication technologies.
  • Cocoa Distributed Objects: Distributed objects are a Cocoa technology that provides a high-level implementation of port-based communication. Although it is possible to use this technique for interthread communication, it is strongly recommended not to do so because of the overhead involved. Distributed objects are better suited for communicating with other processes, although transactions between these processes are also expensive

Port Communication Example

I’m not going to go into detail about the communication between performSelector, which we use a lot in development, but let’s do an example of using a port to pass a message before a thread.

First we create a class to send a message:

@interface WYPerson : NSObject
- (void)personLaunchThreadWithPort:(NSPort *)port;
@end
Copy the code
#import "WYPerson.h"

@interface WYPerson()"NSMachPortDelegate>
@property (nonatomic.strong) NSPort *vcPort;
@property (nonatomic.strong) NSPort *myPort;
@end

@implementation WYPerson


- (void)personLaunchThreadWithPort:(NSPort *)port{
    
    NSLog(@"VC responded in Person");
    @autoreleasepool{✅//1. Save the port passed by the main thread
        self.vcPort = port;
        ✅//2. Set the child thread name
        [[NSThread currentThread] setName:@"WYPersonThread"]; ❗ ️//3. Enable runloop (important here)
        [[NSRunLoop currentRunLoop] run];
        ✅// create your own port
        self.myPort = [NSMachPort port];
        ✅//5. Set port's proxy callback object (NSMachPortDelegate)
        self.myPort.delegate = self;
        ✅//6. Finish sending the message to the main thread port
        [selfsendPortMessage]; }}/** * Finish sending port message to main thread */

- (void)sendPortMessage {
 
    NSData *data1 = [@"WY" dataUsingEncoding:NSUTF8StringEncoding];

    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
    ✅// Send a message to the main thread of the VC// First argument: send time.// msgid message id.// components, send messages with parameters.// Reserved: Indicates the number of bytes reserved for the header
    [self.vcPort sendBeforeDate:[NSDate date]
                          msgid:10086
                     components:array
                           from:self.myPort
                       reserved:0];
    
}

Pragma mark - NSMachPortDelegate handles port passing information

- (void)handlePortMessage:(NSPortMessage *)message{
    NSLog(@"person:handlePortMessage == %@"[NSThread currentThread]);
    NSLog(Some info from VC:);
    NSLog(@"components == %@",[message valueForKey:@"components"]);
    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}


Copy the code

Next we create a PortViewController to receive messages and callback messages to the sender

#import "PortViewController.h"
#import <objc/runtime.h>
#import "WYPerson.h"

@interface PortViewController()"NSMachPortDelegate>
@property (nonatomic.strong) NSPort *myPort;
@property (nonatomic.strong) WYPerson *person;

@end

@implementation PortViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Port thread communication";
    self.view.backgroundColor = [UIColor whiteColor];

    ✅//1. Create a port for the main thread// The child thread sends messages to the main thread through this port
    self.myPort = [NSMachPort port];
    ✅//2. Set the proxy callback object for port
    self.myPort.delegate = self; ❗ ️//3. Add port to runloop, receive port message (the main thread is already running, no need to run)
    [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
    
    self.person = [[WYPerson alloc] init];
    [NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
                             toTarget:self.person
                           withObject:self.myPort];
    
}

#pragma mark - NSMachPortDelegate

- (void)handlePortMessage:(NSPortMessage *)message{
    
    NSLog(@"VC == %@"[NSThread currentThread]);
    
    NSLog(@" Pass some information from person :");
    // An error is reported without this hidden attribute
    //NSLog(@"from == %@",[message valueForKey:@"from"]);
    
    NSArray *messageArr = [message valueForKey:@"components"];
    NSString *dataStr   = [[NSString alloc] initWithData:messageArr.firstObject  encoding:NSUTF8StringEncoding];
    NSLog(@" Pass some information :%@",dataStr);
    NSPort  *destinPort = [message valueForKey:@"remotePort"];
    
    if(! destinPort || ! [destinPort isKindOfClass:[NSPort class]]) {NSLog(@" Incorrect data sent");
        return;
    }
    
    NSData *data = [@ "VC received!!!!!!" dataUsingEncoding:NSUTF8StringEncoding];
    
    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]]; ❗ ️ ❗ ️ ❗ ️// Very important, if you want to receive information in Person's port, you must add the runloop to the current main thread
    [[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];
    
    NSLog(@"VC == %@"[NSThread currentThread]);
    
    BOOL success = [destinPort sendBeforeDate:[NSDate date]
                                        msgid:10010
                                   components:array
                                         from:self.myPort
                                     reserved:0];
    NSLog(@"%d",success);

}


- (void)getAllProperties:(id)somebody{
    
    u_int count = 0;
    objc_property_t *properties = class_copyPropertyList([somebody class], &count);
    for (int i = 0; i < count; i++) {
        const char *propertyName = property_getName(properties[i]);
         NSLog(@ "% @"[NSStringstringWithUTF8String:propertyName]); }}Copy the code

The print result is as follows:

With the above example, we have implemented communication between threads

To summarize the main points of NSPort use:

  1. NSPortObject must be added to the thread that is receiving the messageRunloopIn, must be byRunloopTo manage
  2. The object implementation that receives the messageNSPortDelegateOf the agreement-handlePortMessage:Method to get the message content

The resources

Exploring the underlying principles of iOS – the nature of multithreading

IOS bug fix – Threads