• This post was originally posted on my personal blog:”Unruly Pavilion”
  • Article links:portal

This paper introduces the use and implementation of Pthread and NSThread in iOS multithreading.

Part ONE: The use of pThreads and other related methods.

Part TWO: The use of NSthreads, thread-related usage, thread state control methods, communication between threads, thread safety and thread synchronization, and thread state transition related knowledge.

The Demo I have put on Github, Demo link: portal

1. pthread

1.1 introduction of pthread

Pthread is a set of universal multithreading API, which can be used cross-platform in Unix, Linux, Windows and other systems. Written in C language, it requires programmers to manage the life cycle of threads by themselves, which is quite difficult to use. We almost don’t use Pthread in iOS development. But still can understand.

POSIX Threads (Pthreads for short) is the POSIX standard for threads. The standard defines a set of apis for creating and manipulating threads. On UniX-like operating systems (Unix, Linux, Mac OS X, etc.), Pthreads are used as operating system threads. There is also a port version of PThreads-Win32 for Windows.

POSIX Threads (English: POSIX Threads, often shortened to Pthreads) is the POSIX threading standard that defines a set of apis for creating and manipulating Threads. The library that implements the POSIX threading standard is often called Pthreads and is commonly used on UNIX-like POSIX systems such as Linux and Solaris. But implementations on Microsoft Windows also exist, such as pThreads-W32, a third-party library implemented directly using the Windows API; With the Windows SFU/SUA subsystem, you can use some of the native POSIX apis provided by Microsoft.

1.2 How to Use pThread

  1. The first step is to include the header file#import <pthread.h>
  2. The next step is to create a thread and start the thread to perform the task
Create thread: define a variable of type pthread_t
pthread_t thread;
// 2. Enable the thread to execute the task
pthread_create(&thread, NULL, run, NULL);
Set the state of the child thread to detached. This thread will automatically release all resources when it finishes running
pthread_detach(thread);

void * run(void *param)    // The new thread calls the method with the task to be executed
{
    NSLog(@ "% @"[NSThread currentThread]);

    return NULL;
}
Copy the code
  • pthread_create(&thread, NULL, run, NULL);The meanings of the parameters in
    • The first parameter&threadIs a thread object, a pointer to a thread identifier
    • The second is the thread property, which can be assignedNULL
    • The thirdrunA pointer to a function (run is a function that needs to be executed in a new thread)
    • The fourth is an assignable argument to the running functionNULL

1.3 PThread Other related methods

  • pthread_create()Create a thread
  • pthread_exit()Terminate the current thread
  • pthread_cancel()Interrupts the execution of another thread
  • pthread_join()Blocks the current thread until another thread finishes running
  • pthread_attr_init()Initializes the properties of the thread
  • pthread_attr_setdetachstate()Sets the disjointed state property (which determines whether the thread can be joined when terminated)
  • pthread_attr_getdetachstate()Gets the out-of-state property
  • pthread_attr_destroy()Deletes the attributes of the thread
  • pthread_kill()Send a signal to the thread

2. NSThread

Nsthreads are officially provided by Apple. They are more object-oriented than PThreads. They are easy to use and can directly manipulate thread objects. However, it is also necessary for the programmer to manage the thread lifecycle (mainly creation), and we occasionally use NSThreads during development. For example, we will often call [NSThread currentThread] to display the current process information.

So let’s talk about how nsthreads are used.

2.1 Creating and starting a Thread

  • The thread is created and then started
// 1. Create thread
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// 2. Start thread
[thread start];    // Once the thread is started, the run method of self is executed in thread

// The new thread calls the method with the task to be executed
- (void)run {
     NSLog(@ "% @"[NSThread currentThread]);
}
Copy the code
  • The thread is automatically started after it is created
// 1. The thread is automatically started after it is created
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

// The new thread calls the method with the task to be executed
- (void)run {
     NSLog(@ "% @"[NSThread currentThread]);
}
Copy the code
  • Implicitly create and start a thread
/ / 1. Implicit create and start a thread [self performSelectorInBackground: @ the selector (run) withObject: nil]; - (void)run {NSLog(@"%@", [NSThread currentThread]); }Copy the code

2.2 Thread-related Usage

// Get the main thread
+ (NSThread *)mainThread;    

// Determine whether the thread is the master thread (object method)
- (BOOL)isMainThread;

// Determine whether the thread is the primary thread (class method)
+ (BOOL)isMainThread;    

// Get the current thread
NSThread *current = [NSThread currentThread];

// Thread name -- setter method
- (void)setName:(NSString *)n;    

// Thread name -- getter method
- (NSString *)name;    
Copy the code

2.3 Thread state control method

  • Start thread method
- (void)start;
// The thread enters the ready state -> running state. The thread enters the dead state automatically when the task is completed
Copy the code
  • Block (pause) a thread method
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// The thread is blocked
Copy the code
  • Force a thread to stop
+ (void)exit;
// The thread enters the dead state
Copy the code

2.4 Communication between threads

In development, we often do time-consuming operations in child threads and then go back to the main thread to refresh the UI. This involves communication between the child thread and the main thread. Let’s take a look at the official thread communication method for NSThreads.

// Perform operations on the main thread
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
  // equivalent to the first method with kCFRunLoopCommonModes

// Perform the operation on the specified thread
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5.2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5.2_0);

// Perform the operation on the current thread, calling NSObject's performSelector: related method
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Copy the code

Below is a classic download image DEMO to show communication between threads. The specific steps are as follows:

  1. Start a child thread to download the image.
  2. Go back to the main thread and refresh the UI, display the image in UIImageView.

The DEMO code is as follows:

/** * Create a thread to download the image */
- (void)downloadImageOnSubThread {
    // Call downloadImage in the created child thread to download the image
    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}

/** * Download the image and go back to the main thread to refresh the UI */
- (void)downloadImage {
    NSLog(@"current thread -- %@"[NSThread currentThread]);
    
    // 1. Get imageUrl
    NSURL *imageUrl = [NSURL URLWithString:@"https://ysc-demo-1254961422.file.myqcloud.com/YSC-phread-NSThread-demo-icon.jpg"];
    
    // 2. Read data from imageUrl (download image) -- time consuming operation
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    // Create an image from binary data
    UIImage *image = [UIImage imageWithData:imageData];
    
    // 3. Go back to the main thread for image assignment and interface refresh
    [self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:YES];
}

/** * return to main thread for image assignment and interface refresh */
- (void)refreshOnMainThread:(UIImage *)image {
    NSLog(@"current thread -- %@"[NSThread currentThread]);
    
    // Assign the image to imageView
    self.imageView.image = image;
}
Copy the code

2.5 NSThread Thread security and thread synchronization

Thread safety: If your code is in a process that has multiple threads running at the same time, those threads may be running the code at the same time. If the result of each run is the same as the result of a single thread run, and the values of other variables are the same as expected, it is thread-safe.

If there are only reads and no writes on a global variable or static variable per thread, the global variable is generally thread-safe. If you have multiple threads doing writes (changing variables) at the same time, you generally need to consider thread synchronization, otherwise it may affect thread safety.

Thread synchronization: it can be understood as thread A and thread B cooperate together. When thread A performs to A certain extent, it depends on A result of thread B, so it stops and signals B to run. B does what he says and gives the result to A; A Continue operations.

Here’s a simple example: Two people talking together. Two people cannot speak at the same time to avoid inaudible (operational conflict). Wait for one person to finish (one thread finishes the operation), then the other one speaks (the other thread starts the operation).

Below, we simulate the way of train ticket sales to achieve the security of NSThread and solve the thread synchronization problem.

Scene: There are 50 train tickets in total, and there are two Windows for selling train tickets, one is for Beijing train tickets, the other is for Shanghai train tickets. Tickets will be sold at both Windows until they are sold out.

2.5.1 NsThreads are not thread-safe

Let’s start with code that doesn’t consider thread safety:

/** * Initialize the number of train tickets, the ticket window (not thread safe), and start selling tickets */
- (void)initTicketStatusNotSave {
    // 1. Set the remaining train ticket to 50
    self.ticketSurplusCount = 50;
    
    // 2. Set the thread of Beijing train ticket sales window
    self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
    self.ticketSaleWindow1.name = @" Beijing Train ticket Window";
    
    // 3. Set the thread of Shanghai train ticket sales window
    self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketNotSafe) object:nil];
    self.ticketSaleWindow2.name = @" Shanghai Train ticket Window";
    
    // 4. Start selling train tickets
    [self.ticketSaleWindow1 start];
    [self.ticketSaleWindow2 start];

}

/** * Sell train tickets (not thread safe) */
- (void)saleTicketNotSafe {
    while (1) {
        // If tickets are available, go on sale
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount --;
            NSLog(@ "% @"[NSString stringWithFormat:@" Remaining votes: %ld window: %@".self.ticketSurplusCount, [NSThread currentThread].name]);
            [NSThread sleepForTimeInterval:0.2];
        }
        // If the ticket is sold out, close the ticket window
        else {
            NSLog(@" All train tickets sold out");
            break; }}}Copy the code

Partial results after running are as follows:

As you can see, getting votes without considering thread safety is insane, which obviously doesn’t meet our needs, so we need to consider thread safety.

2.5.2 NSThread Thread security

Thread-safe solution: Locks can be placed on threads so that while one thread is performing the operation, other threads are not allowed to perform the operation. IOS implements thread locking in a number of ways. @ synchronized, NSLock, NSRecursiveLock, NSCondition, NSConditionLock, pthread_mutex, OSSpinLock, atomic(property) Set/GE and so on. For the sake of simplicity, we do not analyze the various lock solutions and performance, but use the simplest @synchronized to ensure thread safety and thus solve the thread synchronization problem.

Consider thread-safe code:

/** * Initialize the number of train tickets, the ticket window (thread safe), and start selling tickets */
- (void)initTicketStatusSave {
    // 1. Set the remaining train ticket to 50
    self.ticketSurplusCount = 50;
    
    // 2. Set the thread of Beijing train ticket sales window
    self.ticketSaleWindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
    self.ticketSaleWindow1.name = @" Beijing Train ticket Window";
    
    // 3. Set the thread of Shanghai train ticket sales window
    self.ticketSaleWindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicketSafe) object:nil];
    self.ticketSaleWindow2.name = @" Shanghai Train ticket Window";
    
    // 4. Start selling train tickets
    [self.ticketSaleWindow1 start];
    [self.ticketSaleWindow2 start];
    
}

/** * sell train tickets (thread safety) */
- (void)saleTicketSafe {
    while (1) {
        / / the mutex
        @synchronized (self) {
            // If tickets are available, go on sale
            if (self.ticketSurplusCount > 0) {
                self.ticketSurplusCount --;
                NSLog(@ "% @"[NSString stringWithFormat:@" Remaining votes: %ld window: %@".self.ticketSurplusCount, [NSThread currentThread].name]);
                [NSThread sleepForTimeInterval:0.2];
            }
            // If the ticket is sold out, close the ticket window
            else {
                NSLog(@" All train tickets sold out");
                break; }}}}Copy the code

After running, the result is:

Omitting part of the result graph…

As can be seen, in consideration of thread safety, after locking, get the correct number of votes, there is no confusion. We have solved the problem of multi-thread synchronization.

2.6 Thread Status Conversion

When we create a new thread NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; , in memory is:

When [thread start] is called; After that, the system puts the thread object into the schedulable thread pool and the thread object enters the ready state, as shown in the figure below.

Of course, there are other thread objects in the schedulable thread pool, as shown in the figure below. Here we only care about the thread object on the left.

Let’s look at the state transition of the current thread.

  • If the CPU now schedules the current thread object, the current thread object enters the running state, and if the CPU schedules another thread object, the current thread object returns to the ready state.
  • If the CPU calls sleep while the current thread object is running, the current thread object is blocked. When sleep is available, the current thread object is ready.
  • If the CPU runs the current thread object while the thread task completes, the current thread object enters the dead state.

The text alone may be confusing, but the current thread object’s state changes as shown below.


IOS multithreading exhaustive summary series of articles:

  • IOS multithreading: a detailed summary of “pthread, NSThread”
  • IOS multithreading: an exhaustive summary of “GCD”
  • IOS multithreading: summary of NSOperation, NSOperationQueue
  • IOS multithreading: a thorough summary of “RunLoop”