Series of articles:OC Basic principle series.OC Basic knowledge series

We talked a lot about threads in the previous article about portals. In this article we will continue to learn about multi-threading, and we will focus on multi-threaded GCD.

The GCD introduction

  • Full name of GCD: Grand Central Dispatch
  • GCD is pure C and provides a lot of powerful functions
  • GCD is a very efficient way of multithreaded development, and it is not part of the Cocoa framework

The GCD advantage

  • 1.GCD is a solution proposed by Apple for multi-core parallel computing
  • 2.GCD will automatically utilize more CPU cores (e.g. Dual-core, quad core)
  • 3.GCD automatically manages thread lifecycle (thread creation, task scheduling, thread destruction)
  • 4. Developers only need to tell the GCD what tasks they want to perform without writing any thread management code

GCD is a function that adds a task to a queue and specifies the function to execute the task.

The GCD use

There are only two things we need to do in GCD use: 1. Define tasks. 2. Add the task to the queue. So the core of GCD is dispatch queues and tasks.

The GCD queues

Here’s how GCD gets the queue centrally:

  • 1. Main thread queue: The submitted tasks will be completed on the main thread
    • This can be obtained by dispatch_get_main_queue().
    • The main queue is the main queue, which is a serial queue. In iOS, only the main queue can have the permission to submit layer information to the rendering service to complete the graphics display. So uI-related operations must be performed on the main thread.
  • 2. Clobal Queue: The global concurrent Queue is shared by the entire process and has four priorities: high, middle (default), low, and background
  • 3. Customize queues
    • Concurrent queue:
      • Global queues are concurrent queues
      • Create with dispatch_queue_create and assign the second parameter to DISPATCH_QUEUE_CONCURRENT, etc
      • Instead of waiting for the last task to complete, start a new thread to execute the new task.
    • Serial queue:
      • Create with dispatch_queue_create and assign the second parameter to DISPATCH_QUEUE_SERIAL or NULL.
      • Serial queues can only perform one task at a time

The overall picture is as follows:

The GCD task

A GCD task is an action, which is how your code in a block executes. There are two ways to execute a task: synchronous and asynchronous. The main differences between the two are whether to wait for the completion of a queued task and whether to open a thread.

Sync Execution

  • 1. The system synchronously adds tasks to a specified queue. Before the added tasks are completed, the system waits until the tasks in the queue are complete.
  • 2. You can only execute tasks in the current thread and cannot start new threads.

Asynchronous execution

  • 1. Asynchronously add a task to the specified queue. The task continues to execute without waiting.
  • 2. Can execute tasks in new threads and start new threads.

Let’s take a look at the basic form of GCD:Now let’s match the queue with the task and see if we can print the result and prepare the code

/** Synchronous concurrent */ - (void)concurrentSyncTest{dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_CONCURRENT);  for (int i = 0; i<10; I++) {dispatch_sync (queue, ^ {NSLog (@ "synchronization of concurrent - % d - % @", I, [NSThread currentThread]); }); }} /** Async concurrency */ - (void)concurrentAsyncTest{dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i<10; I ++) {dispatch_async(queue, ^{NSLog(@" async -%d-%@", I,[NSThread currentThread]); }); }} /** serial async */ - (void)serialAsyncTest{dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_SERIAL); for (int i = 0; i<10; I ++) {dispatch_async(queue, ^{NSLog(@" -%d-%@", I,[NSThread currentThread]); }); } /** serial synchronization */ - (void)serialSyncTest{dispatch_queue_t queue = dispatch_queue_create("LJ", DISPATCH_QUEUE_SERIAL); for (int i = 0; i<10; I++) {dispatch_sync (queue, ^ {NSLog (@ "serial synchronous - % d - % @", I, [NSThread currentThread]); }); }}Copy the code

Based on the task execution mode and different queue combinations, we can draw the following conclusions by printing information:

  • 1. The task execution method isAsynchronous or synchronous only determines whether to start a new thread. Synchronous (no thread is created), asynchronous (new thread is created)
  • 2. The queue isParallel or serial only determines whether to open multiple threads. Serial (open only one thread), parallel (open multiple threads,The ability to open up multiple threads is only useful in asynchronous execution)
  • 3.Asynchronous parallel execution of tasks is out of order.

A deadlock

The main cause of deadlocks is when tasks wait for each other, as shown in the following code:Run code:Error found, the cause of error is deadlock. There are three steps to this method:

  • Task 1: Line 132 prints task 1, which is in the main thread.
  • Task 2: Line 137 prints task 3
  • Task 3: Lines 134-136 insert the print 2 task into the main thread by synchronizing the task

We know thatThe main thread is the synchronization taskTask 1 and Task 2 add the main thread first.Mission three will come after mission one and two. butTask three is to join by synchronizing tasks. This leads to the following situation,Task 3 will be executed after the main thread completes task 1 and task 2. whileThe synchronization task will cause task 2 to wait for task 3 to completeAnd that’s what caused itIn the main thread, task 3 waits for task 2 to completeIn theTask 2 appears in the synchronization task. Wait until task 3 completesThis creates mutual waiting. The occurrence of a deadlock crash as shown below is easier to explain:

A preliminary study on the principle of THE COMMUNIST Party of China

Above we said GCD tasks and queues, and through the code print to clarify the relationship between tasks and queues, let’s look at the bottom implementation of GCD

Locate the SOURCE code for GCD research

We wanted to explore THE GCD, only to find that we didn’t know where to start, and once the code was clicked in, we couldn’t go any further. So how do we know where the source code for threads is? We have to determine the source code, we knowThe dispatch_queue_create method creates threadsSo let’s try interruptingRun the codeAt this point you can be sure that the source code for the thread is in libdispatch.dylib. We downloaded the libdispatch. Dylib source code from the official Apple documentation.

Dispatch_get_main_queue ()

Let’s start with the dispatch_get_main_queue() main thread Here is an explanation of the main thread:

  • Line 569-570:The main queue is the main thread and main runloop used to interact within the application context.
  • Line 579-580:The main queue is automatically created and is created before the main() function

It is called in front of main(), which is done in the dyLD procedure

Dispatch_get_main_queue () revisited

Let’s print the main thread, and see what the main thread looks like, okay

	dispatch_queue_t serial = dispatch_queue_create("Lj", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t conque = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
    NSLog(@"%@-%@-%@-%@",serial,conque,mainQueue,globQueue);
Copy the code

Print runWe see that by printing the resultsThe main thread is OS_dispatch_queue_main: com.apple.main-thread,The underlying system is named com.apple.main-thread. Let’s search the libdispatch source for com.apple.main-threadWe see that the main function is a structure object, and we see that the important DQF_WIDTH (behavior width, as a very important marker) is 1 (1 is serial, single-threaded), and dq_serialnum is also 1. We see that this structure is _dispatch_main_q, and it says that the main thread was created very early, so let’s see when _dispatch_main_q is called. We searched for _dispatch_main_q and found a lot

There are eight files in 42 places

So what should we do?

libdispatch_init

Multithreaded calls were originally created in theDyld load is the thread load: libdispatch_init OC basic principle – DYLD load process slave gate, search libdispatch_init (So we’ve seen a lot of libdispatch_init methods, and we’re talking about the main method, so let’s seeIn line 7759, the do_targetq of the static structure _dispatch_main_q is equal to _dispatch_get_default_queue(true). The back isPerform a series of operations on _dispatch_MAIN_q (line 7762: set current main queue, line 7763: bind to corresponding thread). Let’s look at the binding process: _dispatch_queue_set_bound_thread.The underlying implementation of the binding is handled by the OS_atomic_RMW_loop2O method, which is not included in the libDispatch source code and will be discussed later.

conclusion

Below the main thread is the _dispatch_main_q structure, which is created in the dyly load using the libdispatch_init method and is the serial equivalent of a queue.

dispatch_get_global_queue

Let’s click to see:Dispatch_get_global_queue requires two parameters: identifier and flags, which are described in the comment:

  • Identifier: Quality of Service (Priority)
  • Flags: Reserved

becauseIf there is a priority, there can be multiple dispatch_get_global_queues in the entire projectSo how to design it? We can think ofCollect dispatch_get_global_queue by collectionThe dispatch_get_global_queue global queue is dispatch_get_global_queue.

The above discoveries are all created using the _DISPATCH_ROOT_QUEUE_ENTRY method. There are various global concurrent queues with different priorities.

The current structure is _dispatch_root_queues, which is also a static structure.

How to create DISPATCH_QUEUE_SERIAL and DISPATCH_QUEUE_CONCURRENT

Dispatch_get_main_queue () and dispatch_get_global_queue are static structures. Now we mainly talk about the creation of queues, and the implementation principle of serial and parallel

dispatch_queue_create

Take a look at the underlying implementation

Dispatch_queue_create is created with _dispatch_lane_CREATE_with_target and the parameters are label and attr. DISPATCH_TARGET_QUEUE_DEFAULT and true are the default values

Let’s look at the internal implementation of _dispatch_lane_create_with_targetIt’s a long method. How do we study it? We only need to focus on line 2809 of the return value. It returns our thread. Let’s look at the _dispatch_trace_queue_CREATE method

We see that the dQ is passed in by the _dispatch_introspection_queue_create method and the DQIC is created first (line 653, dQIC_queue._dq is assigned to dQIC on line 654). Line 659 also assigns the do_finalizer for DQ to dQIC. It then returns _dqu for upcast(dq).

There’s nothing on it that we want. Let’s go back to the _dispatch_lane_create_with_target method and look at the return valuereturn _dispatch_trace_queue_create(dq)._dq;This method returns _dq, as we know aboveThe dQ returned by _dispatch_introspection_queue_CREATE is the same as the assigned DQ. We just need to look at the DQ passed in by _dispatch_lane_create_WITH_target.At this point the DQ is created

We see in the init method that we see the dqai.dqai_concurrent property, which affects the thread

Dqai.dqai_concurrent determines the value of width, 1172 lines DOF_WIDTH() is the number of threads supported by the queue, 1 is serial (single thread), >1 is parallel (multiple threads), If dqai.dqai_concurrent is true it is multi-threaded, otherwise it is single-threaded.

Let’s look at the creation of the DQAIThe dQA we pass in is the value we pass in. Let’s look at the _dispatch_queue_attr_to_info implementationWe see that dqai.dqai_Concurrent is related to IDX, which is related to DQA. Dqa is the value we pass in when we create the thread (DISPATCH_QUEUE_CONCURRENT or DISPATCH_QUEUE_SERIAL).If dQA ==&_dispatch_queue_attr_concurrent is true, it is multithreaded.

Let’s go back to the _dispatch_lane_create_with_target methodIn the case of serial, the vtable assignment is passed as queue_concurrent. In the case of parallel vtable assignment, the value is passed as queue_serial for assignment purposes. Vtable is also an object The preceding figure shows that the parallel queue Vtable is OS_dispatch_queue_concurrent_class, and the serial queue Vtable is OS_dispatch_queue_serial_class. OS_dispatch_queue_serial specifies the number of concurrent and serial queues.We find that the results of serial and parallel printing are in agreement with those predicted above.

Let’s go back to the _dispatch_lane_create_with_target method and continue with the _dispatch_object_alloc method. We are a_os_OBJect_alloc_realizedWe already know thatVtable is assigned differently in serial and parallelIn the _os_object_alloc_realizedThe vtable value is the CLS, line 1509: Create yesPointer isa to CLS to vTABLE.

conclusion

The bottom line of queue creation is that _dispatch_lane_CREATE_WITH_target is created by passing in a value to determine whether it’s a serial queue or a parallel queue. Dispatch_queue_t is also an object, and it’s also created by alloc init. Alloc sets the ISA pointer to concurrent or serial, using init to determine properties such as DOF_WIDTH().

dispatch_async

Now let’s look at the implementation of asynchronous tasks

  • Dq: It’s the queue that goes through
  • Work: The tasks that come in

Let’s see how the code works, okay

  • Line 890: Create dc
  • Line 896: Task wrapper, used to receive, save blocks

Line 2633-2638: Save work to dc_ctxt (dc_dispatch_continuation_init_f). Note: func executes the method on line 2642, meaning that the func will be destructed or released after execution.

Now we see that the parameters above correspond to CTXT and f. Method save CTXT and f to dc_ctxt and dc_func properties of dc respectively.

Explore work execution in dispatch_async

We know from above that work is a task. Let’s exploreAt this point, work is printing 123456, we interrupt, run, and then bt

We can see that start_wqthread, _pthread_wqthread is in the libsystem_pthread source code, and the first method in the libdispatch source code is _dispatch_worker_thread2. Let’s search

_dispatch_worker_thread2

The red box is the code executed belowThe _dispatch_continuation_pop_inline method is executed in the loop at lines 6581-6588

The last thing that gets called is the F (CTXT) method, which we talked about above with the _dispatch_continuation_init_f method of dispatch_async, and then the last thing that gets called is the task method in F, the task in CTXT, which is validated here.

Finally, _dispatch_call_block_AND_RELEASE in the _dispatch_continuation_init method is calledThis is the entire process of executing a block task.

expand

Related interview questions

【 Interview question-1 】 Asynchronous function + parallel queue what is the result printed below?

- (void)textDemo2{ dispatch_queue_t queue = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); // dispatch_async(queue, ^{NSLog(@"2")); dispatch_async(queue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); }Copy the code

Answer: 1,5,2,4,3

Solution: queue is a concurrent queue and does not block threads, so 1,5 are executed first. Parallel queues contain parallel queues, so they don’t affect each other. So 2,4 prints first, and 3 prints last.

Code changes

[Modification 1] : Change the parallel queue to serial queue, no effect on the result, the order is still 1 5 2 4 3

[Modification 2] : Before task 5, sleep 2s, namely sleep(2), is executed in the following order: 1, 2, 4, 3, 5. The reason is that I/O printing is simpler than sleep 2s, so asynchronous block1 will be executed before task 5. Of course, if the main queue is blocked, there will be other execution orders.

NSLog(@”3″) will be printed; “Dispatch_async” instead of “dispatch_sync”. The execution sequence is as follows: 1,5,2,3,4. Cause: changing the previous asynchrony to synchronous enough will block the thread that prints 2. As a result, printing 4 can be executed only after printing 3 is finished.

【 interview question 2】 Asynchronous function nested synchronous function + serial queue (i.e. synchronous queue)

- (void)textDemo2{dispatch_queue_t queue = dispatch_queue_CREATE ("Lj", NULL); NSLog(@"1"); // dispatch_async(queue, ^{NSLog(@"2")); dispatch_sync(queue, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); }Copy the code

Answer: 1,5,2 crash

The reason: Serial queue, the queue is 1,5,2 normal print no longer explain, execution after 2 (2, the current thread for serial), print the 3 tasks by synchronizing task is inserted into the serial queue, placed behind the print 4 (in 2 serial tasks, 4 of print in the front of the 3), but the synchronization tasks necessary to perform a 3 in 4, is waiting for each other, Cause a deadlock.

[Modification] : Will print 4 removed?

  • It still deadlocks, because task 3 waits for asynchronous blocks to complete, and asynchronous blocks wait for task 3 to complete, causing a deadlock

【 Interview question-3 】 Asynchronous function + synchronous function + concurrent queue

What is the order in which the following code is executed? (AC) - (void)interview04{dispatch_queue_t queue = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (queue, ^{NSLog(@"1"); }); dispatch_async(queue, ^{ NSLog(@"2"); }); Dispatch_sync (queue, ^{NSLog(@"3"); }); NSLog(@"0"); dispatch_async(queue, ^{ NSLog(@"7"); }); dispatch_async(queue, ^{ NSLog(@"8"); }); dispatch_async(queue, ^{ NSLog(@"9"); }); } A: 1230789 B: 1237890 C: 3120798 D: 2137890Copy the code

Answer: AC

  • 1. Task 1 and task 2 have no fixed order because they are asynchronous functions and concurrent queues, which start threads
  • 2. Similarly, tasks 7, 8, and 9 will start threads, so there is no fixed sequence
  • Task 3 is the synchronization function + the concurrent queue. The synchronization function blocks the main thread, but only blocks 0, so it is certain that 0 must be after 3 and before 789

How many types of queues are there in the following code?

- Serial Dispatch Queue dispatch_queue_t serialQueue = dispatch_queue_create("Lj", NULL); -concurrent Dispatch Queue dispatch_queue_t concurrentQueue = dispatch_queue_create("Lj", DISPATCH_QUEUE_CONCURRENT); -dispatch Queue dispatch_queue_t mainQueue = dispatch_get_main_queue(); -global Dispatch Queue dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);Copy the code

1. SerialQueue: serialQueue, mainQueue 2. Concurrent queues: concurrentQueue, globalQueue

I already said that. I won’t say it here.