• Common multithreading schemes in iOS

Nsthreads, GCD, and NSOpration encapsulate PThreads.

Commonly used functions

GCD has two functions for performing tasks

  • Perform tasks synchronously
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block)
Copy the code
1. Queue: queue 2. Block: taskCopy the code
  • Perform tasks asynchronously
dispatch_async(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block)
Copy the code

GCD source: github.com/apple/swift…

Confusing terms

There are four confusing terms: synchronous, asynchronous, concurrent, serial (all in OC)

  • Synchronization and asynchrony affect whether new threads can be started
    • Synchronization: Executes tasks in the current thread without the ability to start a new thread
    • Asynchrony: Perform a task in a new thread and have the ability to open a new thread (the word “have” does not necessarily open a new thread)
  • Concurrency and serialization affect: how tasks are executed
    • Concurrency: Multiple tasks are executed concurrently (simultaneously)
    • Serial: After one task is executed, the next task is executed

The following is my own understanding, may be wrong:

  1. Synchronization: When a function call is made, the call does not return or continue until the result is received.
  2. Asynchronous: After a call is made, the caller can continue to perform subsequent operations without receiving the result.
  3. Concurrent: “micro Concurrent, macro Concurrent” Concurrent
  4. Parallel: multi-core CPU. When one CPU is executing a thread, another CPU can execute another thread. The two threads can run simultaneously without occupying CPU resources.
  5. Serial: queue execution. Serial

The GCD queues

GCD queues can be divided into two main types

  • Concurrent Dispatch Queue

1. The task can be executed concurrently (automatically enable multiple threads to execute tasks at the same time) 2. Concurrency is only available with asynchronous (dispatch_async) functions

  • Serial Dispatch queues allow tasks to be executed one after another (after a task is completed, the next task is executed)

The execution effect of various queues

The interview questions

The interview questions 1

The @implementation ViewController // dispatch_sync and dispatch_async controls whether or not to open a new thread (in most cases async will not open a thread if the main queue is passed in), The type of queue does not determine whether the task will be executed serially or concurrently. It determines how the task will be executed (concurrent, serial) 1. */ - (void)interview01 {// Question: The following code is executed on the main thread. Will! NSLog(@" Perform task 1"); dispatch_queue_t queue = dispatch_get_main_queue(); Dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL); Not dispatch_sync(queue, ^{NSLog(@" perform task 2"); }); NSLog(@" Perform task 3"); } - (void)interview02 {// question: the following code is executed on the main thread. Don't! NSLog(@" Perform task 1"); dispatch_queue_t queue = dispatch_get_main_queue(); Dispatch_async (queue, ^{NSLog(@" perform task 2"); }); NSLog(@" Perform task 3"); // dispatch_async does not require immediate execution of tasks on the current thread} - (void)interview03 { Will! NSLog(@" Perform task 1"); dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL); Dispatch_async (queue, ^{// 0 NSLog(@" task 2")); Dispatch_sync (queue, ^{// 1 NSLog(@" perform task 3"); }); NSLog(@" Perform task 4"); }); NSLog(@" Perform task 5"); } - (void)interview04 {// question: the following code is executed on the main thread, will cause deadlock? Don't! NSLog(@" Perform task 1"); dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL); // dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL); Dispatch_async (queue, ^{// 0 NSLog(@" task 2")); Dispatch_sync (queue2, ^{// 1 NSLog(@" perform task 3"); }); NSLog(@" Perform task 4"); }); NSLog(@" Perform task 5"); } - (void)interview05 {// question: the following code is executed on the main thread, will cause deadlock? Don't! NSLog(@" Perform task 1"); dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (queue, ^{// 0 NSLog(@" task 2")); Dispatch_sync (queue, ^{// 1 NSLog(@" perform task 3"); }); NSLog(@" Perform task 4"); }); NSLog(@" Perform task 5"); } - (void)viewDidLoad { [super viewDidLoad]; [self interview01]; // [self interview02]; // [self interview03]; // [self interview04]; // [self interview05]; }Copy the code
  1. Interview01: will be deadlocked. The main queue of the main thread, with interview01, when executed todispath_syncYes, the main queue increases the number of tasks in the block due to the queue, the FIFO (First in First out), and due todispatch_syncMust wait while the current thread synchronizes tasks and fetches tasks from the block immediatelyinterview01Execute, andinterview01And indispatch_syncHere, all cause a deadlock.

Dispatch_queue_t queue = dispatch_queue_create(“myqueue”, DISPATCH_QUEUE_SERIAL) No deadlocks. It is still in the main thread, but the task is not in the main queue, so it can be fetched and executed.

  1. Interview02: Does not deadlock. becausedispatch_asyncThere is no requirement to execute tasks synchronously in the current thread immediately, so there is no blocking.
Note: When dispatch_async is sent to the main queue, no thread is started and the task is executed serially, but it is not required to execute the task synchronously on the current thread immediately. Don't be confused that the execution renderings of the various queues are correct. Dispatch_async does not equal dispatch_sync when passed to the main queueCopy the code
  1. Interview03: This will cause deadlock. A serial queue has block0. Block1 two tasks. When executing sync sentence, sync executes block1 and must wait until block0 is executed. So deadlocks.

  2. Interview04: Does not cause deadlock. Because block0 and block1 are not in the same serial queue

  3. Interview05: Does not cause deadlock. Because block0 and block1 are in the same concurrent queue, block1 can be removed and executed without waiting for block0 to finish executing.

  • Summary: Using sync to add tasks to the current serial queue will lock the current serial queue.

dispatch_queue_t

Dispatch_queue_t is a pointer.

- (void)viewDidLoad {
    [super viewDidLoad];
        
    dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue3 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue4 = dispatch_queue_create("queu4", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue5 = dispatch_queue_create("queu4", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"%p %p %p %p %p", queue1, queue2, queue3, queue4, queue5);  
}
Copy the code

Print result:

  • The global queue address is the same. There is only one global queue address
  • Queues created by themselves have different addresses even if their names are the same, such as Queue4 and Queue5. Try not to use the same name because there are apis that can fetch queues based on the name.

The interview questions 2

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"1"); // Add timer to Runloop [self performSelector:@selector(test) withObject:nil afterDelay:.0]; NSLog(@"3"); // [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }); }Copy the code

The printed result is: 1, 3 Reasons:

  1. PerformSelector: withObject: afterDelay: is the essence of started a timer, add this into the Runloop timer.
  2. The child thread does not start the timer by default. The timer was added to the Runloop but the Runloop did not start. So add this sentence[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];After that, the 2 will print.[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];Don’t add this sentence because Runloop already has an event: NSTimer.
NSLog(@"1"); // Add timer to Runloop [self performSelector:@selector(test) withObject:nil afterDelay:.0]; NSLog(@"3");Copy the code

1,3,2. This is because Runloop wakes up to handle NSTimer. Click events are processed before hibernation, so 3 prints first.

[self performSelector:<#(SEL)#> withObject:<#(id)#>]; Nsobject.mm in runtime source code. Essentially the underlying call objc_msgSend sends a message. [self performSelector:@selector(test) withObject:nil afterDelay:.0]; It comes from Runloop in Foundation.

GNUstep

Runloop,NSString,NSArray, and so on in Foundation are not open source, but GNUstep has re-implemented the OC library in Cocoa as an open source implementation

  • GNUstep, a project of the GNU Project, is an open-source re-implementation of the OC library in Cocoa
  • Source address: www.gnustep.org/resources/d…
  • Although GNUstep is not an official source code for Apple, it is a good reference

The interview question 3

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSThread *thread = [[NSThread alloc] initWithBlock:^{ NSLog(@"1");  // [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];  // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }]; [thread start]; [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES]; }Copy the code

[thread start]; [thread start]; And then it’s going to go block, and then it’s going to go [self performSelector:@selector(test) onThread: Thread withObject:nil waitUntilDone:YES]; However, after the block is finished, the thread exits, and running test on the thread will crash. The solution is to add the following two sentences:

//        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
Copy the code

Discussion on GCD queue group

Consider: How to use GCD to achieve the following functions

  • Asynchronously execute task 1 and task 2 concurrently
  • After task 1 and task 2 are completed, return to the main thread and perform task 3
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {// create a dispatch_group_t group = dispatch_group_create(); Dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT); Dispatch_group_async (group, queue, ^{for (int I = 0; i < 5; I++) {NSLog (@ "task 1 - % @", [NSThread currentThread]); }}); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 5; I++) {NSLog (@ "task 2 - % @", [NSThread currentThread]); }}); // After the previous task is completed, // dispatch_group_notify(group, queue, ^{dispatch_async(dispatch_get_main_queue(), ^{for (int I = 0; i < 5; I++) {/ / NSLog (@ "task 3 - % @", [NSThread currentThread]); / / / /}}); / /}); // dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // for (int i = 0; i < 5; I++) {/ / NSLog (@ "task 3 - % @", [NSThread currentThread]); / / / /}}); dispatch_group_notify(group, queue, ^{ for (int i = 0; i < 5; I++) {NSLog (@ "task 3 - % @", [NSThread currentThread]); }}); dispatch_group_notify(group, queue, ^{ for (int i = 0; i < 5; I++) {NSLog (@ 4 - % @ "task", [NSThread currentThread]); }}); }Copy the code
  • dispatch_group_notifyAfter performing all tasks in the queue group, the system goes to dispatch_group_notify to perform the final task.
  • Task 1 and task 2 above are simultaneous and must be printed first. Same thing for task 3 and task 4, it doesn’t have to be who prints first

Perform task 1 and task 2 before task 3 and Task 4.