Application of multithreading in iOS development

1, Foreword

Multithreading schemes that can be used in iOS development include:

  • pthread: a cross platform multithreading solution implemented in pure C language, which is difficult to use. It is not recommended on iOS platform.
  • NSThread: the object-oriented thread object under iOS platform is relatively easy to use, but it needs developers to manage its life cycle, and multiple thread synchronization needs to be used together with locks such as NSLock.
  • Grand Central Dispatch (GCD): the multithreading solution of pure C API under iOS platform hides many technical details, such as no need to manually create threads, manage thread life cycle and use various locks, so that developers can focus on business logic itself.
  • NSOperationQueue: object-oriented encapsulation based on GCD. For operations with dependencies and canceling tasks in the queue, only relatively simple settings are required.

2, Actual use

1.NSThread

1.1 display creation thread

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  //Create a thread.
[thread start];  // Start thread
// As soon as the thread starts, it will execute the run method of self in the thread

1.2 create and call anonymously

[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
//Or:
[self performSelectorInBackground:@selector(run) withObject:nil];

1.3 significance of parameters

  • Selector: a method executed by a thread. This selector can only have one parameter and cannot have a return value.  
  • target: the object sent by the selector message.
  • argument: the only parameter passed to target, or nil.

The first method is to create a thread object first, and then run the thread operation. Before running the thread operation, you can set the thread priority (qualityOfService) and other thread information. The second method will directly create a thread and start running the thread.

1.4 thread life cycle

Start thread
- (void)start; 
// Enter ready status - > running status. When the thread task is completed, it will automatically enter the dead state

Blocking (pausing) threads
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// Enter blocking state

Force thread stop
+ (void)exit;
// Enter a state of death

Note: once the thread stops (dies), the task cannot be started again. Only one thread object can be re created.

1.5 thread safety

When NSThread objects access shared resources, specifically, multiple threads may access the same resource. For example, multiple threads access the same object, the same variable and the same file, which is easy to cause data confusion and data security problems.

The thread safety problem can be solved through mutual exclusion (@ synchronized), atomic attribute (atomic) and other methods, but both methods consume CPU resources.

1.6 inter thread communication

Embodiment of inter thread communication: one thread transfers data to another thread. After performing a specific task in one thread, go to another thread to continue to perform the task. Common methods of inter thread communication:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;//Run the method in the main thread. Wait indicates whether to block the call of this method. If YES, wait for the end of the method running in the main thread. It can generally be used to call UI methods in sub threads.
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;//Execute in the specified thread, but the thread must have run loop.

tips: if the method needs to be executed in the specified sub thread, the specified sub thread needs to turn on runloop to keep alive. Add these two lines of code to the running method of the sub thread:

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];

1.7 wide application

Generally speaking, it is used to judge whether it is in the main thread [NSThread isMainThread], and to obtain the main thread [NSThread mainThread]. In addition, it is used for a time-consuming task. For example, loading network pictures needs to be executed in the background. After loading, go to the main thread to display pictures. It is troublesome to use in multithreaded environment. Generally, this type is not used.

2. GCD

In GCD, there is no need to deal with the contents related to threads. Just select the appropriate queue (serial or parallel) and determine whether the task blocks are executed synchronously or asynchronously. A thread pool will be automatically maintained within the system to complete the tasks in the queue.

2.1 create serial queue

dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);

/**
Synchronous serial task
*/
dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"Is%@Perform synchronous serial task 1",[NSThread isMainThread] ? @"Main thread" : @"Child thread");
    });
NSLog(@"Task 2");

/********************************************************************************
//Console print results:
Executing synchronous serial task 1 on child thread
 Task 2
********************************************************************************/


/**
Asynchronous serial task
*/
dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"Is%@Perform asynchronous serial task 1",[NSThread isMainThread] ? @"Main thread" : @"Child thread");
    });
NSLog(@"Task 2");

/********************************************************************************
//Console print results:
Task 2
 Executing asynchronous serial task 1 on child thread
********************************************************************************/

Therefore, adding a task to a serial queue, whether synchronous or asynchronous, will create a sub thread to execute the task. However, asynchronous execution will not block the current thread, so "task 2" will output first.  

2.2 creating parallel queues

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
   dispatch_sync(concurrentQueue, ^{
       NSLog(@"Task 1 in%@", [NSThread currentThread]);
    });
}
    
NSLog(@"Main thread task");

/********************************************************************************
//Console print results:
Task 1 is in < nsthread: 0x6000004c4680 > {number = 7, name = (null)}
Task 1 is in < nsthread: 0x6000004c4680 > {number = 7, name = (null)}
Task 1 is in < nsthread: 0x6000004c4680 > {number = 7, name = (null)}
Task 1 is in < nsthread: 0x6000004c4680 > {number = 7, name = (null)}
Task 1 is in < nsthread: 0x6000004c4680 > {number = 7, name = (null)}
Main thread task
********************************************************************************/

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Task 1 in%@", [NSThread currentThread]);
    });
}
    
NSLog(@"Main thread task");

/********************************************************************************
//Console print results:
Main thread task
 Task 1 is in < nsthread: 0x6000008b1700 > {number = 6, name = (null)}
Task 1 is in < nsthread: 0x6000008a8800 > {number = 7, name = (null)}
Task 1 is in < nsthread: 0x6000008b0e40 > {number = 4, name = (null)}
Task 1 is in < nsthread: 0x6000008b1700 > {number = 6, name = (null)}
Task 1 is in < nsthread: 0x6000008a8800 > {number = 7, name = (null)}
********************************************************************************/

It can be seen from this that if tasks are added to the parallel queue in a synchronous manner, the system will only use one sub thread to process them, block the current thread, and wait until all the tasks in the queue are executed. If all tasks are added asynchronously, the system will create multiple sub threads to process these tasks at the same time without blocking the current thread. While the sub thread processes the queue tasks, the current thread continues to execute the following code.

2.3 creating task groups

The role of the task group is to listen to the task completion event after the task is executed in the queue and do the next operation in the thread specified by the developer. Example code:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"task%d stay%@",i, [NSThread currentThread]);
        dispatch_group_leave(group);
    });
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"to update UI");
});


NSLog(@"Main thread task");


/********************************************************************************
//Console print results:
Main thread task
 Task 0 is in < nsthread: 0x60001688540 > {number = 5, name = (null)}
Task 1 is in < nsthread: 0x60001694740 > {number = 7, name = (null)}
Task 2 in < nsthread: 0x6000001686240 > {number = 6, name = (null)}
Task 4 in < nsthread: 0x60001688540 > {number = 5, name = (null)}
Task 3 is in < nsthread: 0x6000016ff240 > {number = 4, name = (null)}
Update UI
********************************************************************************/

2.4 signal quantity

//Create semaphore
dispatch_semaphore_t lock = dispatch_semaphore_create(0);
//Semaphore plus one
dispatch_semaphore_signal(lock);
//The blocking function will reduce the semaphore by one. After that, if the semaphore is greater than or equal to 0, it will not block, otherwise the current thread will be blocked
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);

2.4.1 application of semaphore

        1. Controls the maximum number of concurrent queues to prevent creating too many threads that consume too many system resources.

//Define maximum concurrent number
#define MAX_ConCurrent_Count 5
dispatch_semaphore_t signal = dispatch_semaphore_create(MAX_ConCurrent_count);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 200; i++) {
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        dispatch_semaphore_signal(signal);
    });
}

        2. As a lock, it makes the access to shared resources thread safe in multi-threaded environment.

NSMutableSet *threadSets = [[NSMutableSet alloc] init];
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 200; i++) {
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        //It ensures that at most one thread enters here at the same time
        [threadSets addObject:[NSThread currentThread]];
        dispatch_semaphore_signal(lock);
    });
}

2.5 thread safe singleton

Insurance practices for creating unique policy cases in OChttps://blog.csdn.net/fang20180409/article/details/117691109

2.6 GCD timer

/**
   A one-time timer that executes only once after a specified time
*/
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^(void){
    NSLog(@"GCD-----%@",[NSThread currentThread]);
});


/**
   Repeatable timer, which will be executed every other period of time
*/
@property (nonatomic ,strong)dispatch_source_t timer;

//0. Create queue
dispatch_queue_t queue = dispatch_get_main_queue();
//1. Create timer in GCD
/*
The first parameter: create the type dispatch of source_ SOURCE_ TYPE_ Timer: timer
 Second parameter: 0
 Third parameter: 0
 Fourth parameter: queue
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//2. Set time, etc
/*
First parameter: timer object
 Second parameter: DISPATCH_TIME_NOW means to start timing from now on
 The third parameter: interval time. The minimum unit of time in GCD is nanosecond
 The fourth parameter: accuracy (indicates the allowable error, and 0 indicates absolute accuracy)
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

//3. Task to call
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD-----%@",[NSThread currentThread]);
});

//4. Start implementation
dispatch_resume(timer);

//5. Strong reference is required, otherwise the timer will not work
self.timer = timer;

//If you want to cancel the timer
//dispatch_source_cancel(self.timer);

2.7 # barrier function

/**
There are two fence functions, dispatch_barrier_sync and dispatch_barrier_async. Pay special attention when using this function. It must only be applicable to user-defined concurrent queues. Global queues and serial queues are invalid. Its function is equivalent to setting dependencies. After the tasks added before the fence function are executed, the tasks added after the fence function can be executed.
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"Non Queue Task 1");
dispatch_async(concurrentQueue, ^{
    NSLog(@"Task 1");
});
dispatch_barrier_sync(concurrentQueue, ^{
    NSLog(@"Synchronous fence method");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"Task 2");
});
dispatch_barrier_async(concurrentQueue, ^{
    NSLog(@"Asynchronous fence method");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"Task 3");
});
NSLog(@"Non Queue Task 2");



/********************************************************************************
//Console print results:
Non Queue Task 1
 Task 1
 Synchronous fence method
 Task 2
 Non Queue Task 2
 Asynchronous fence method
 Task 3
********************************************************************************/

2.8 GCD task block

The task block in GCD is dispatch_block, you can monitor whether the task block is completed, and you can do something else after the task block is completed.

dispatch_block_t task = dispatch_block_create(0, ^{
    NSLog(@"Executing block Time consuming task");
    [NSThread sleepForTimeInterval:2];
});
dispatch_queue_t concurrentQueue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, task);

//You can use this function to cancel a task that has not been started, but you cannot cancel a task that is being executed
//dispatch_block_cancel(task);

//Block the current thread until the task block is executed. If the return value is equal to 0, the execution is completed, and if it is greater than 0, the task timeout is not completed.
long time = dispatch_block_wait(task, DISPATCH_TIME_FOREVER);
if (time == 0) {
    NSLog(@"The task is completed");
}
//It will not block the current thread. After the task is completed, it will listen to the notification of the block variable, and can choose to update the UI in the main queue or perform other operations
dispatch_block_notify(task, dispatch_get_main_queue(), ^{
    NSLog(@"To update UI");
});


/********************************************************************************
//Console print results:
Executing block time consuming task
 The task is completed
 To update the UI
********************************************************************************/

3. NSOperationQueue

NSOperationQueue class is an object-oriented encapsulation based on GCD and has powerful functions. Compared with GCD, it is simpler and easier to use, and the code is more readable.

Keywords: iOS objective-c

Added by matthewod on Sun, 20 Feb 2022 20:47:02 +0200