In order to prepare for further study of iOS multi-threading, we first need to understand the theory of multi-threading. In order to solve some problems in schedule development, we have basically used GCD, NSOperation, NSThread and pthread. What is multi-threading and what does it include? Must it be good to use multiple threads?

This article is a reorganization of the theoretical knowledge.

1. What is a thread

Thread (English: Thread) is the smallest unit of operation scheduling and the basic execution unit of a process. All tasks of a process are executed in threads. In order for a process to execute tasks, it must have threads. A process must have at least one thread. By default, a thread is started for iOS applications. This thread is called the master thread.

2. What is process

  • Broad definition: a process is a program with some independent function about a data set of a running activity. It is the basic unit of dynamic execution of operating system. In traditional operating system, process is both the basic unit of allocation and the basic unit of execution.

  • Narrow definition: A process is an application that is running in the system.

Each process has its own address space, which generally includes text regions, data regions, and stack regions.

For iOS, it is a single process. The sandbox mechanism designed by Apple will block the interference between apps in iOS system, making apps more secure.

Open Explorer, each line is a process, each process has its own name, PID, etc.

The relationship and difference between threads and processes

Understand the definition of thread and process, thread and process relations and differences are mainly divided into the following points:

  • Address space: Processes have independent address Spaces. Threads of the same process share the address space of the current process. Threads in one process are not visible to other processes.

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

  • Error: When an error occurs in a process, only the process crashes. Other processes are not affected. When a thread crashes, the entire process crashes.

  • Scheduling and switching: when process switching, resource consumption is large, thread context switching is much faster than process context switching, need to frequently switch and share resources, need to use multithreading;

  • Execution process: Each independent process has a program entry and sequential execution sequence, showing that threads can not be independently executed, need to be attached to the process, a process for scheduling;

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

4. The meaning of multithreading

Multithreading is to solve the problem of CPU idle when a single process is waiting for I/O operation, and to solve the problem that the whole process is blocked when a single thread is blocked.

The following benefits are evident when we use multithreading:

  • Appropriately improve the efficiency of program execution;
  • Improve resource utilization, such as memory,CPUAnd so on;
  • The task on the thread is automatically destroyed after it completes execution.

However, while multithreading provides convenience, it also brings many disadvantages:

  • Starting threads takes up a certain amount of memory (512 KB for each thread by default)
  • Starting a large number of threads takes up a lot of memory and reduces program performance
  • Will make the program design more complex, such as: communication between threads, data sharing between threads, resources contention between threads, etc.

Therefore, we need to reasonably use multi-threading within the scope of our ability, open a reasonable number of threads, so as to improve the efficiency of program execution, but also will not occupy too many resources.

5, the principle of multi-threading

Take a single core CPU for example, opened 5 threads, each thread simply printed a string, found in the console, 5 strings basically printed out at the same time, at this time, feel multithreading is multiple threads at the same time. No, look at the picture below:

There are three threads on the diagram. Each solid black line represents the time the CPU is currently executing a task, and the dotted line represents the wait time. As you can see, the CPU is just switching back and forth between threads, but the switching speed is very fast.

The CPU divided the task scheduling into a lot of time slices, between the time slices constantly switch, only to create the illusion of output at the same time, to achieve true multithreading, need multi-core CPU, if it is dual-core CPU, then the unified time is 2 tasks at the same time.

6. Thread lifecycle

As a developer, as long as it’s an object, it has a life cycle.

When a thread is created and started, it is neither in the execution state as soon as it is started nor always in the execution state. In its life cycle, a thread passes through five states: New, Runnable, Running, Blocked, and Dead. Especially when a thread is started, it cannot always “hog” the CPU and run on its own, so the CPU has to switch between threads, so the thread state will switch between running and blocking many times.

  • New: When the program creates a thread, the thread is in the new state, at this time only by the process to allocate memory for it, and initialize the value of its member variables;

  • Ready: The thread is ready after the start() method is called on the thread object. The process creates a method call stack and a program counter for it to wait for the schedule to run;

  • Running: A thread in the ready state is running if it has acquired the CPU and starts executing the thread body of the run() method;

  • Blocking: A running thread enters a blocking state when it loses the resources it occupies.

  • Death: Task completed or forced to quit.

Principle of thread pool

In multithreading, each thread needs to be created, but after creation, each thread needs to be managed and reclaimed, and this operation is done by the thread pool.

For a thread pool, it is the size of the rating, so when the new task need of threads, thread pool will need to see if their core thread pool size is smaller than the first, if you have resources, you can directly open a thread to perform a task, if the core thread pool is full, you will need to determine whether the work queue in the thread pool is full, if you have spare queue, Tasks can be pushed into the queue and executed in the order of the queue FIFO. If there are no free queues, the following policy choices can be made:

  • AbortPolicy: the default policy, new task submitted directly throws RejectedExecutionException of abnormal detection, the exception can be caught by the caller;

  • CallerRunsPolicy: To adjust the mechanism, neither discard the task nor throw an exception. Instead, the task is rolled back to the caller. Instead of executing the new task in the thread of the thread pool, the new task is run in the thread calling exector.

  • DiscardPolicy: The newly submitted task is discarded.

  • DiscardOldestPolicy: Discards the task with the longest waiting time and tries a new submitted task.

8. Communication between threads

Communication between threads is when one thread passes data to another thread, and when a particular task is completed in one thread, it is transferred to another thread to continue the task.

Common methods of communication between threads:

  • Based on theNSThread:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5.2_0);

Copy the code
  • Based on theGCD:
- (void)dispatch_async_function {
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {/// subthread operations
        
        / / /...
        
        // return to the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            / / /...
        });
    });
}
Copy the code
  • Based on theport:
// viewcontroller.m

- (void)function {
    /// 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 and receive port message
    [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
    
    self.person = [[KCPerson alloc] init];
    [NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
                             toTarget:self.person
                           withObject:self.myPort];
}

// model.m

- (void)personLaunchThreadWithPort:(NSPort *)port{
    
    NSLog(@"VC responds to Person");
    @autoreleasepool {
        /// 1. Save the port passed by the main thread
        self.vcPort = port;
        
        /// 2. Set the child thread name
        [[NSThread currentThread] setName:@"KCPersonThread"];
        
        /// 3. Enable runloop
        [[NSRunLoop currentRunLoop] run];
        
        // 4. Create your own port
        self.myPort = [NSMachPort port];
        
        /// 5. Set the proxy callback object for port
        self.myPort.delegate = self;
        
        ///6. Finish sending the message to the main thread port[self sendPortMessage]; }}/** * Finish sending port message to main thread */

- (void)sendPortMessage {
 
    NSData *data1 = [@"name" dataUsingEncoding:NSUTF8StringEncoding];
    NSData *data2 = [@"address" 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];
    
}
Copy the code

Relationship between threads and queues

A queue consists of one or more tasks that are assigned to a thread when they are ready to be executed. When there are multiple system cores, in order to run efficiently, the cores will assign multiple threads to each core to perform tasks. For the system core, there is no concept of task. For a parallel queue, tasks may be assigned to multiple threads for execution, i.e., the parallel queue may correspond to multiple threads. For serial queues, it corresponds to one thread at a time, and that thread may not change, it may be replaced.

A thread can only perform one task at a time. A thread can also be idle or suspended, so it doesn’t have to be performing tasks while it exists. Queues and threads are two hierarchies. Queues are abstract structures for ease of use and understanding, while threads are system-level units of scheduling. They are hierarchically related.

10, Thread and runloop relationship

The relationship between threads and runloops is as follows:

  • Runloops correspond to threads one by one. A runloop corresponds to a core thread. Runloops can be nested, but there can only be one core, and their relationship is stored in a global dictionary.

  • Runloop is used to manage threads. When a thread’s Runloop is enabled, the thread will go to sleep after executing a task. When a task is available, it will be woken up to execute the task.

  • The runloop is created on the first fetch and destroyed at the end of the thread;

  • For the main thread, the runloop is created by default as soon as the program starts. For the child thread, the runloop is lazily loaded and only created when we use it. Therefore, when using the child thread, make sure that the runloop is created for the child thread, otherwise the timer will not call back.

11, endnotes

Here, multi-threaded theoretical knowledge is over, more boring, but these things for a developer or need to understand, with these basic knowledge, to really start multi-threaded source research.