This is the fifth day of my participation in the August More text Challenge. For details, see:August is more challenging

1. The GCD

Grand Central Dispatch, introduced in Mac OS X V10.6, is another alternative to threading, allowing you to focus on the task that needs to be performed rather than thread management. Using GCD, you can define tasks to be executed and add them to a work queue that handles the scheduling of tasks on the appropriate thread. Work queues take into account the number of available cores and the current load to perform tasks more efficiently than using threads. GCD is a pure C language that provides a lot of powerful functions. Its advantages:

  • GCD is apple’s solution for multi-core parallel computing

  • – GCD will automatically utilize more CPU cores (e.g. Dual-core, quad-core)

  • GCD automatically manages the thread life cycle (create thread, schedule task, destroy thread). The programmer just needs to tell

  • What task does GCD want to perform without writing any thread management code

1.1 the function

The block of a task has no parameters and no return value

* Function to perform the task \

  • asynchronousdispatch_async

* The next statement can be executed without waiting for the current statement to complete

* will enable the thread to execute the block task

* Asynchronous is a synonym for multithreading

  • synchronousdispatch_sync

* You must wait for the current statement to complete before executing the next statement

* Threads are not started

* The task that executes the block at the current time

1.2 Serial queues and parallel queues

Queue: Waiting queue for the task to be executed, that is, the queue used to store the task. A queue is a special type of linear table that follows the first-in, first-out (FIFO) principle, in which new tasks are always inserted to the end of the queue and tasks are read from the beginning. Each time a task is read, a task is released from the action queue. Queues are divided into serial queues and parallel queues

Serial queue: only execute one task at a time, wait for the previous task to complete before executing the next task, can be imagined as a serial circuit, in one direction, can also be called a guard, the last task did not finish, can not go to the next task.

// Serial queue
dispatch_queue_t queue = dispatch_queue_create("keke", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue1 = dispatch_queue_create("serial_k", NULL);//NULL indicates the default serial queue
Copy the code

Parallel queue: multiple tasks can be executed at a time, and the next task can be executed without waiting for the last task to complete. You can imagine a parallel circuit, when it encounters a diversion in one direction, it will all go. You can perform multiple tasks at the same time.

// Create a parallel queue
dispatch_queue_t queue = dispatch_queue_create("kebei", DISPATCH_QUEUE_CONCURRENT);
Copy the code

1.3 Functions and queues

Functions have synchronous functions and asynchronous functions, queues have synchronous queues and asynchronous queues, so they have four cases.

1.3.1 Synchronous function serial queue

- (void)kb_testSyncFunctionSerialQueue

{

    NSLog(@"Current thread - %@", [NSThread currentThread]);

    

    dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(serialQueue, ^{

        sleep(2);

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@"Task 1");

    });

    

    dispatch_sync(serialQueue, ^{

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@Task 2 "");

    });

    

}
Copy the code

  • The thread is not started and the task is executed on the current thread.
  • The tasks are executed in sequence, one after the other.
  • It creates a blockage.

1.3.2 Synchronizing function concurrent queues

- (void)kb_testSyncFunctionInConcurrentQueue

{

    NSLog(@"Current thread - %@", [NSThread currentThread]);

    

    dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_sync(concurrentQueue, ^{

        sleep(2);

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@"Task 1");

    });

    

    dispatch_sync(concurrentQueue, ^{

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@Task 2 "");

    });



}
Copy the code

  • The thread is not started and the task is executed on the current thread.
  • The tasks are executed in sequence, one after the other.
  • No blockage

1.3.3 Asynchronous function serial queue

- (void)kb_testAsyncFunctionInSerialQueue

{

    

    NSLog(@"Current thread - %@", [NSThread currentThread]);



    dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);

    

    dispatch_async(serialQueue, ^{

        sleep(2);

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@"Task 1");

    });

    

    dispatch_async(serialQueue, ^{

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@Task 2 "");

    });

  

}
Copy the code

  • A new thread is started and the new thread performs the task.
  • Task after task after task.
  • There is no blockage.

1.3.4 Asynchronous function concurrent queue

- (void)kb_testAsyncFunctionInConcurrentQueue

{

    NSLog(@"Current thread - %@", [NSThread currentThread]);

    dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_async(concurrentQueue, ^{

        sleep(2);

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@"Task 1");

    });

    

    

    dispatch_async(concurrentQueue, ^{

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@Task 2 "");

    });


}
Copy the code

  • Start a thread to execute tasks on the current thread.
  • Tasks are executed asynchronously, in no order, and are dependent on CPU scheduling.
  • There is no blockage.

1.4 Main queue and global queue

  • The home side column

If a task is currently executing on the main thread, then whatever task is currently being added to the main queue will not be scheduled, okay

dispatch_queue_t mainQueue = dispatch_get_main_queue();
Copy the code
  • Global concurrent queue

For the convenience of programmers, Apple provides a global queue. A global queue is a concurrent queue that can be used for asynchronous tasks if there is no special need for a queue in multithreaded development

dispatch_queue_t globQueue = dispatch_get_global_queue(0.0);
Copy the code

1.4.1 Main queue and synchronization functions

Main queue to executeNSLog andSynchronizing block Tasks. synchronousblockI have to wait for the blockThe print taskIt has to be donesynchronous, butBlock Print taskAnd etc.Block synchronizationThen print. soWait for each other, resulting inA deadlock.

Deadlock phenomenon

  • The main thread becauseSynchronization functionThe reason whyTo perform firstTask.
  • Wait in the main queueThe main threadThe task ofcompleteIn the implementationTask of one's own.
  • Main queue and main threadWait for each otherCause a deadlock.

1.4.2 Main queuing and asynchronous functions

Don't open upNew thread, current threadAccording to the orderperform

1.4.3 Global queue and synchronization functions

Synchronization functionDon't open upNew threads, tasksAccording to the orderperform

1.4.4 Global queues and asynchronous functions

taskconcurrentExecute, not in order,Will open upNew threads.

2. The COMMUNIST Party often meets with the exam questions

2.1 There are several types of queues

// Serial Dispatch Queue - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("kb_serialQueue", NULL);
    
// Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
// Main Queue - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
// Global Concurrent Queue - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0.0);


Copy the code

Two, serial queues and parallel queues. The main queue is essentially a serial queue, defining the identity of main, and the thread number is 1. The global queue is essentially a parallel queue.

2.2 Interview questions in the sequence of weibo thread execution

The following code is executed in order

- (void)wbinterDemo{//

    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);


    dispatch_async(queue, ^{ / / time consuming

        NSLog(@"1");

    });

    

    dispatch_async(queue, ^{

        NSLog(@"2");

    });

    

    // Block which row

    dispatch_sync(queue, ^{ NSLog(@"3"); });

    

    NSLog(@"0");


    dispatch_async(queue, ^{

        NSLog(@"Seven");

    });

    dispatch_async(queue, ^{

        NSLog(@"8");

    });

    dispatch_async(queue, ^{

        NSLog(@"9");

    });

    // A: 1230789

    // B: 1237890

    // C: 3120798

    // D: 2137890

}

Copy the code

The answer is A | C

c Analysis:Concurrent queue,An asynchronous functionThere is noExecution order1 and 2 are possible first and then 3Synchronization functionWill execute play to execute the following, therefore123It’s execution first,Particular order; 0 is the sameAfter stepSo 3 is finished and then 0,7,8,9 is executed asynchronously but allIt's going to be after 0, order reason, 7,8,9 itself concurrency is not in order. so123 out of order - 0-789 out of order0 must be in the fourth place, so let’s pickAandC.

  • What do I choose to replace the above queue with a serial queue
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
Copy the code

A, in order to execute, in the serial queue according to the first-in, first-out principle, asynchronous function will open up A new thread to execute the task, synchronous function will not, but in the serial queue according to the FIFO principle.

2.3 Asynchronous Functions The call order of nested asynchronous functions

- (void)textDemo{

    

    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"1");

    / / time consuming

    dispatch_async(queue, ^{

        NSLog(@"2");

        dispatch_async(queue, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });


    NSLog(@"5");


}
Copy the code

The call sequence is: 1,5,2,4,3. The analysis is as follows:

  1. Execute sequentially in the main thread1-> concurrent queue asynchronous function ->5, because 5 is relatively easy. I did 5 first;
  2. In an asynchronous function2-> asynchronous function 3->4Because 4 is easy, we did 4 first, then 3;
  3. So the execution sequence print result isOne, five, two, four, three

Add sleep for simulation:

  • Extension: if the above queue is changed to serial queueDISPATCH_QUEUE_SERIALWhat is the print result?

The print is 1,2,5,4,3 or 1,5,2,4,3. Analysis is as follows:

  1. The main thread executes in sequence1-> serial queue asynchronous function ->5Because 5 is easy. I did 5 first, but it’s possiblejamIt’s going to go into the asynchronous notationblock, will finish 2 first, I appear in the process of printingA 2 at a 5Before.
  2. Enter theBlocks for asynchronous functionsTo execute the tasks in sequence2->4-> serial queue asynchronous function 3Since 4 is in the so-called main thread of the serial queue, the child thread will wait for the main thread to completeExecution subthread.
  3. You can addSlepp (1)To block the thread simulation

Generally asynchronous functions will open up new threads, queue concurrent execution, child thread asynchronous functions will open up new threads, because it is concurrent so according to how much time to print the order according to the time.

In a serial queue, the task is to press oneA sequential executionSo theySharing a threadTask 4 is inCreated in the thread, will be finished before theThe thread is given to task 3To carry out the mission, and thenPass it to Task 13This process does notIn the course of timeJust,In order to execute.

2.4 Asynchronous function nesting synchronous function call order

- (void)textDemo2{

    // Synchronize the queue

    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"1");

    // Asynchronous function

    dispatch_async(queue, ^{

        NSLog(@"Current thread - %@", [NSThread currentThread]);

        NSLog(@"2");

        / / synchronize

        dispatch_sync(queue, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");



}
Copy the code

Execute in order 1,5,2,3,4. Analysis:

  1. Outer execution 1-> asynchronous function ->5, execute concurrent tasks inside the asynchronous function.
  2. Internal execution 2-> sync function (clogging thread) -> 4.

Changing a concurrent queue to a serial queue causes a deadlock

Asynchronous function block,Synchronization function 3Completed toPerform four, but in the synchronization queue againSuccessful task 4 is the big block completionCan only bePerform Task 3, which is equivalent toTask 3 and task 4Wait for each other to finishWe need it to go.

Remove task 4 and it still crashes because of thisThe big blockCannot execute, regardless of whether the task is executed, the synchronization function has locked the thread.

3. Principle analysis of queues and functions

We know that there are four kinds of queues, primary queues, global queues, serial queues and concurrent queues. But it’s actually two, and that’s what we analyzed. So how is the queue implemented? We need to look at the libDispatch source download link

The home team in 3.1

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW

dispatch_queue_main_t

dispatch_get_main_queue(void)

{

return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);

}
Copy the code

The main queue is automatically created, calledmain()Before the main thread, the main queue was used to interact in the application context.

Continue to look atDISPATCH_GLOBAL_OBJECTThe macros defined

#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
Copy the code

The first parameter is the type, we’re passing dispatch_queue_main_t, and the second parameter is the object and we’re passing _dispatch_main_q. Because the main queue is created before the main() function. We search for where _dispatch_main_q = was created

// 6618342 Contact the team that owns the Instrument DTrace probe before

// renaming this symbol

// A static variable modifier means that it is created only once and is a structure type

struct dispatch_queue_static_s _dispatch_main_q = {

DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),//

#if! DISPATCH_USE_RESOLVERS .do_targetq = _dispatch_get_default_queue(true),

#endif

.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |

DISPATCH_QUEUE_ROLE_BASE_ANON,/ / state

.dq_label = "com.apple.main-thread"./ / name

.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),/ / logo

.dq_serialnum = 1./ / number

};
Copy the code
  • We also have a convenient method

We print four types of queues where<#const char * _Nullable label#>Is the name of the queue, so the main queue should be the defaultcom.apple.main-threadThe name of the main queue, golibdispatchsearch

That’s exactly what we analyzed before.

3.2 Global Queue

The global queue is aConcurrent queue, usually passed in a queuepriority, and flag (thread overload).

dispatch_queue_global_t

dispatch_get_global_queue(intptr_t priority, uintptr_t flags)

{

dispatch_assert(countof(_dispatch_root_queues) ==

DISPATCH_ROOT_QUEUE_COUNT);


if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {

return DISPATCH_BAD_INPUT;// Queue with overcommit means that every time a task is committed, the system will open a new thread to process it, so that no thread will overcommit.

}

dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);// Get the priority

#if! HAVE_PTHREAD_WORKQUEUE_QOSif (qos == QOS_CLASS_MAINTENANCE) {

qos = DISPATCH_QOS_BACKGROUND;// Require these tasks to run before other tasks. Using this QOS class indicates that work should run in the most energy efficient and efficient way possible.

} else if (qos == QOS_CLASS_USER_INTERACTIVE) {

qos = DISPATCH_QOS_USER_INITIATED;// This type of work requires a lower priority than critical user interaction work, but a higher priority than other work on the system. This is not an energy-saving QOS class for large tasks. Its use should be limited to a short enough duration so that users are less likely to switch tasks while waiting for results. A typical user-initiated task will indicate progress by displaying placeholder content or a modal user interface.

}

#endif

if (qos == DISPATCH_QOS_UNSPECIFIED) {

return DISPATCH_BAD_INPUT;

}

return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);

}
Copy the code

Continue to _dispatch_get_root_queue

DISPATCH_ALWAYS_INLINE DISPATCH_CONST

static inline dispatch_queue_global_t

_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)

{

if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {

DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");// The priority is in the range

}

return &_dispatch_root_queues[2 * (qos - 1) + overcommit];

}
Copy the code

The system creates queues 4-15

struct dispatch_queue_global_s _dispatch_root_queues[] = {

#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \

((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \

DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \

DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)

#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \

[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \

DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \

.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \

.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \

.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \

.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \

_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \

_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \

__VA_ARGS__ \

}

_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,

.dq_label = "com.apple.root.maintenance-qos",

.dq_serialnum = 4,

),

_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.maintenance-qos.overcommit",

.dq_serialnum = 5,

),

_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,

.dq_label = "com.apple.root.background-qos",

.dq_serialnum = 6,

),

_DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.background-qos.overcommit",

.dq_serialnum = 7,

),

_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,

.dq_label = "com.apple.root.utility-qos",

.dq_serialnum = 8,

),

_DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.utility-qos.overcommit",

.dq_serialnum = 9,

),

_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,

.dq_label = "com.apple.root.default-qos",

.dq_serialnum = 10,

),

_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,

DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.default-qos.overcommit",

.dq_serialnum = 11,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,

.dq_label = "com.apple.root.user-initiated-qos",

.dq_serialnum = 12,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.user-initiated-qos.overcommit",

.dq_serialnum = 13,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,

.dq_label = "com.apple.root.user-interactive-qos",

.dq_serialnum = 14,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.user-interactive-qos.overcommit",

.dq_serialnum = 15,),};Copy the code

3.3 Serial queues and concurrent queues

3.3.1 dispatch_queue_t

Both serial and concurrent queues used in daily development are of type Dispatch_queue_T.

typedef struct dispatch_queue_s *dispatch_queue_t;// is a structure of type dispatch_queue_s
// Keep looking
#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

_DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \

/* LP64 global queue cacheline boundary */ \

unsigned long dq_serialnum; \

const char *dq_label; \

DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \

const uint16_t dq_width, \

const uint16_t __dq_opaque2 \

); \

dispatch_priority_t dq_priority; \

union { \

struct dispatch_queue_specific_head_s *dq_specific_head; \

struct dispatch_source_refs_s *ds_refs; \

struct dispatch_timer_source_refs_s *ds_timer_refs; \

struct dispatch_mach_recv_refs_s *dm_recv_refs; \

struct dispatch_channel_callbacks_s const*dch_callbacks; The \}; \ int volatile dq_sref_cnt struct dispatch_queue_s { DISPATCH_QUEUE_CLASS_HEADER(queue,void *__dq_opaque1);

/* 32bit hole on LP64 */

} DISPATCH_ATOMIC64_ALIGN;
// The main one is _DISPATCH_QUEUE_CLASS_HEADER👇 Check more
#if OS_OBJECT_HAVE_OBJC1

#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

DISPATCH_OBJECT_HEADER(x); \

DISPATCH_UNION_LE(uint64_t volatile dq_state, \

dispatch_lock dq_state_lock, \

uint32_t dq_state_bits \

); \

__pointer_sized_field__

#else

#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

DISPATCH_OBJECT_HEADER(x); \

__pointer_sized_field__; \

DISPATCH_UNION_LE(uint64_t volatile dq_state, \

dispatch_lock dq_state_lock, \

uint32_t dq_state_bits \

)
// Continue to view DISPATCH_OBJECT_HEADER 👇
#define DISPATCH_OBJECT_HEADER(x) \

struct dispatch_object_s _as_do[0]; \

_DISPATCH_OBJECT_HEADER(x)

\
// Continue to view _DISPATCH_OBJECT_HEADER👇
#endif
#define _DISPATCH_OBJECT_HEADER(x) \

struct _os_object_s _as_os_obj[0]; \

OS_OBJECT_STRUCT_HEADER(dispatch_##x); \

struct dispatch_##x##_s *volatile do_next; \

struct dispatch_queue_s *do_targetq; \

void *do_ctxt; \

union { \

dispatch_function_t DISPATCH_FUNCTION_POINTER do_finalizer; \

void*do_introspection_ctxt; The \}// Go to OS_OBJECT_STRUCT_HEADER👇
#define OS_OBJECT_STRUCT_HEADER(x) \

_OS_OBJECT_HEADER(\

const void *_objc_isa, \

do_ref_cnt, \

do_xref_cnt); \

const struct x##_vtable_s *do_vtable
// Continue to view _OS_OBJECT_HEADER👇
#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \

        isa; /* must be pointer-sized and use __ptrauth_objc_isa_pointer */ \

        int volatile ref_cnt; \

        int volatile xref_cnt
Copy the code

We’ve stripped the threads and found the dispatch_queue_T structure to store all of our defined lable, priority, specific, isa, reference count, etc.

3.3.2 rainfall distribution on 10-12 dispatch_queue_create

We know that queues are essentially structures that store the label, priority, and so on that we define. Let’s go ahead and see how do I create it

dispatch_queue_t

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

{

return _dispatch_lane_create_with_target(label, attr,

DISPATCH_TARGET_QUEUE_DEFAULT, true);

}
DISPATCH_NOINLINE

static dispatch_queue_t

_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,

dispatch_queue_t tq, bool legacy)

{

dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

\


//

// Step 1: Normalize arguments (qos, overcommit, tq)

// Handle normalization parameters, priority, thread overload, queue type

\


dispatch_qos_t qos = dqai.dqai_qos;// Processing priority

#if! HAVE_PTHREAD_WORKQUEUE_QOSif (qos == DISPATCH_QOS_USER_INTERACTIVE) {

dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;

}

if (qos == DISPATCH_QOS_MAINTENANCE) {

dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;

}

#endif / /! HAVE_PTHREAD_WORKQUEUE_QOS

\


_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;

if(overcommit ! = _dispatch_queue_attr_overcommit_unspecified && tq) {if (tq->do_targetq) {

DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "

"a non-global target queue"); }}// If the queue type priority is overloaded, a new thread is created

if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {

// Handle discrepancies between attr and target queue, attributes win

if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {

if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {

overcommit = _dispatch_queue_attr_overcommit_enabled;

} else{ overcommit = _dispatch_queue_attr_overcommit_disabled; }}if (qos == DISPATCH_QOS_UNSPECIFIED) {

qos = _dispatch_priority_qos(tq->dq_priority);

}

tq = NULL;

} else if(tq && ! tq->do_targetq) {// target is a pthread or runloop root queue, setting QoS or overcommit

// is disallowed

if(overcommit ! = _dispatch_queue_attr_overcommit_unspecified) { DISPATCH_CLIENT_CRASH(tq,"Cannot specify an overcommit attribute "

"and use this kind of target queue"); }}else {

if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {

// Serial queues default to overcommit!overcommit = dqai.dqai_concurrent ? _dispatch_queue_attr_overcommit_disabled : _dispatch_queue_attr_overcommit_enabled; }}if(! tq) { tq = _dispatch_get_root_queue( qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;if(unlikely(! tq)) { DISPATCH_CLIENT_CRASH(qos,"Invalid queue attribute"); }}//

// Step 2: Initialize the queue

//

\


if (legacy) {

// if any of these attributes is specified, use non legacy classes

if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {

legacy = false; }} \const void *vtable;// Concatenation queue name

dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;

if (dqai.dqai_concurrent) {

vtable = DISPATCH_VTABLE(queue_concurrent);// Queue type

} else {

vtable = DISPATCH_VTABLE(queue_serial);

}

switch (dqai.dqai_autorelease_frequency) {

case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:

dqf |= DQF_AUTORELEASE_NEVER;

break;

case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:

dqf |= DQF_AUTORELEASE_ALWAYS;

break;

}

if (label) {// Concatenate the queue name

const char *tmp = _dispatch_strdup_if_mutable(label);

if(tmp ! = label) { dqf |= DQF_LABEL_NEEDS_FREE; label = tmp; } } \ dispatch_lane_t dq = _dispatch_object_alloc(vtable, sizeof(struct dispatch_lane_s));// Open queue memory

_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?

DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |

(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));// Queue initialization

\


dq->dq_label = label;// Assign the name of the label queue

dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,

dqai.dqai_relpri);// Assign priority

if (overcommit == _dispatch_queue_attr_overcommit_enabled) {

dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;/ / assignment

}

if(! dqai.dqai_inactive) { _dispatch_queue_priority_inherit_from_target(dq, tq); _dispatch_lane_inherit_wlh_from_target(dq, tq); } _dispatch_retain(tq); dq->do_targetq = tq;// Binding

_dispatch_object_debug(dq, "%s", __func__);

return _dispatch_trace_queue_create(dq)._dq;// Queue processing

}
typedef struct dispatch_queue_attr_info_s {

dispatch_qos_t dqai_qos : 8;

int      dqai_relpri : 8;

uint16_t dqai_overcommit:2;

uint16_t dqai_autorelease_frequency:2;

uint16_t dqai_concurrent:1;

uint16_t dqai_inactive:1;

} dispatch_queue_attr_info_t;

\
Copy the code

There are two main steps

  1. The first step:Normalized parameter.Processing queue priorityThere are six: userInteractive> Default > Unspecified >userInitiated> Utility > BackgroundOverload threadQueues that support overCOMMIT are created when the queue is createdWhether or not the system has enough resourcesWill restart a thread).
  2. The second step:Processing queue name, the queuetype(serial or parallel),Initializing a queue.The bindingRelationship,Queue processing.
  • [Step 1]dqaicreate

//
typedef struct dispatch_queue_attr_info_s {

dispatch_qos_t dqai_qos : 8;

int      dqai_relpri : 8;

uint16_t dqai_overcommit:2;

uint16_t dqai_autorelease_frequency:2;

uint16_t dqai_concurrent:1;

uint16_t dqai_inactive:1;

} dispatch_queue_attr_info_t;

Copy the code

The dispatch_queue_attr_info_t type is created to store queue priorities, overloaded threads and other information.

  • [Step 2] Set the attributes associated with the queue, such as queuePriority qosEtc.
  • Step 3: PassDISPATCH_VTABLESplicing queue name
  • [Step 4] Initializealloc + init, the queue type is set, isa points to the DQF_WIDTH(width) type

Alloc:

void *

_dispatch_object_alloc(const void *vtable, size_t size){#if OS_OBJECT_HAVE_OBJC1

const struct dispatch_object_vtable_s *_vtable = vtable;

dispatch_object_t dou;

dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);/ / isa point

dou._do->do_vtable = vtable;// Set the type

return dou._do;

#else

return _os_object_alloc_realized(vtable, size);

#endif

}
Copy the code

Init:

static inline dispatch_queue_class_t

_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf, uint16_t width, uint64_t initial_state_bits)

{

uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);

dispatch_queue_t dq = dqu._dq;



dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |

DISPATCH_QUEUE_INACTIVE)) == 0);


if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {

dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume

if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {

dq->do_ref_cnt++; // released when DSF_DELETED is set

}

}


dq_state |= initial_state_bits;

dq->do_next = DISPATCH_OBJECT_LISTLESS;

dqf |= DQF_WIDTH(width);// Serial or concurrent Width is itself the number of concurrent queues, which is 1 for serial queues and is unlimited for concurrent queues

os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);

dq->dq_state = dq_state;

dq->dq_serialnum =

os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);

return dqu;

}

Copy the code
  • [Step 5] Process the created queue_dispatch_trace_queue_createDo some processing actually_dispatch_introspection_queue_create
dispatch_queue_class_t

_dispatch_introspection_queue_create(dispatch_queue_t dq)

{

dispatch_queue_introspection_context_t dqic;

size_t sz = sizeof(struct dispatch_queue_introspection_context_s);// Calculate the size


if(! _dispatch_introspection.debug_queue_inversions) { sz = offsetof(struct dispatch_queue_introspection_context_s, __dqic_no_queue_inversion); } dqic = _dispatch_calloc(1, sz);

dqic->dqic_queue._dq = dq;

if (_dispatch_introspection.debug_queue_inversions) {

LIST_INIT(&dqic->dqic_order_top_head);

LIST_INIT(&dqic->dqic_order_bottom_head);

}

dq->do_introspection_ctxt = dqic;


_dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);

LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);

_dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);


DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);

if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {

_dispatch_introspection_queue_create_hook(dq);

}

return upcast(dq)._dqu;

}
Copy the code

_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> As you can see from the _dispatch_introspection_lane_get_info, unlike our custom classes, the underlying implementation of creating queues is created using templates

static inline dispatch_introspection_queue_s

_dispatch_introspection_workloop_get_info(dispatch_workloop_t dwl)

{

uint64_t dq_state = os_atomic_load2o(dwl, dq_state, relaxed);

dispatch_introspection_queue_s diq = {

.queue = dwl->_as_dq,

.target_queue = dwl->do_targetq,

.label = dwl->dq_label,

.serialnum = dwl->dq_serialnum,

.width = 1,

.suspend_count = 0,

.enqueued = _dq_state_is_enqueued(dq_state),

.barrier = _dq_state_is_in_barrier(dq_state),

.draining = 0,

.global = 0,

.main = 0};return diq;

}
Copy the code

3.4 Principle analysis of synchronization function

The source code is as follows:Continue to see

DISPATCH_ALWAYS_INLINE

static inline void

_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,

dispatch_function_t func, uintptr_t dc_flags)

{

if (likely(dq->dq_width == 1)) {// The synchronization function

return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);// Initialize the fence function

}


if(unlikely(dx_metatype(dq) ! = _DISPATCH_LANE_TYPE)) { DISPATCH_CLIENT_CRASH(0."Queue type doesn't support dispatch_sync");

}


dispatch_lane_t dl = upcast(dq)._dl;

// Global concurrent queues and queues bound to non-dispatch threads

// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE

if(unlikely(! _dispatch_queue_try_reserve_sync_width(dl))) {return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);/ / a deadlock

}


if (unlikely(dq->do_targetq->do_targetq)) {

return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);

}

_dispatch_introspection_sync_begin(dl);// Process the current information

_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(

_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));// The concurrent queue is called

}
Copy the code

What is the process? We add the corresponding symbol breakpoint for viewingLet’s see ifwidth ==1 Is a synchronous function call_dispatch_barrier_sync_fConcurrent queues are called_dispatch_sync_invoke_and_complete. Look at the first_dispatch_barrier_sync_f

Thread id = thread ID = thread ID = thread ID = thread ID

Continue to look at__DISPATCH_WAIT_FOR_QUEUE__

Enter the_dq_state_drain_locked_by Continue to enter theJudgment will beThe scheduling ofandThe current waiting queueIs it the same? If it is, return YES, resulting in a deadlockDISPATCH_CLIENT_CRASH. If no deadlock is generated, execute_dispatch_trace_item_pop()Outbound execution. So how do we do the scheduling_dispatch_sync_invoke_and_complete_recurse

Look at the_dispatch_sync_function_invoke_inline:Look at thecallout:

You can see the final execution quite clearlyF functionThis is from the outsideThe callback block. The synchronization function consists of three main steps

  • To queue a task:_dispatch_thread_frame_push
  • Block callback for executing a task:_dispatch_client_callout
  • To send out a mission:_dispatch_thread_frame_pop

3.5 Principle analysis of asynchronous Functions

void

dispatch_async(dispatch_queue_t dq, dispatch_block_t work)

{

dispatch_continuation_t dc = _dispatch_continuation_alloc();

uintptr_t dc_flags = DC_FLAG_CONSUME;

dispatch_qos_t qos;

qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);// Process the block callback work

_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);// Concurrent processing

}
Copy the code

View the task wrapper _dispatch_continuation_init

How do we know who’s leavingreturn, continue to useSymbol breakpoint

Continue to see_dispatch_continuation_init_f

savefunctodispatch_continuation_t, in the handdcAssign values to thedispatch_qos_tIn the.

  • _dispatch_continuation_async processing

The core isDx_push macro definitionTo see the

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
Copy the code

Essentially, dQ_push is called on dX_vtable (which is actually do_push on the object). Take a closer look at DQ_push

Different queues perform differentdq_pushAdd a symbol breakpoint to verify the global queue used in our code queue.

Continue to see_dispatch_root_queue_push

To view

As you can see, the tasks that we loaded into the custom are thrown into the root queue that they are attached to, so the queue we created ourselves is just an agent, continuing with _dispatch_root_queue_poke

To view_dispatch_root_queue_poke_slow:

Look at the first_dispatch_root_queues_initThe implementation of the:

static inline void

_dispatch_root_queues_init(void)

{

dispatch_once_f(&_dispatch_root_queues_pred, NULL,

_dispatch_root_queues_init_once);

}

Copy the code

Dispatch_once_f is a singleton, which we’ll see later. The realization of the _dispatch_root_queues_init_once

Look at the_dispatch_workloop_worker_thread

To view_dispatch_worker_thread2

To view _dispatch_root_queue_drain To view _dispatch_continuation_pop_inline

So the first thing we did was to see if there was a vtable (_dispatch_object_has_vtable), here’s whydispatch_barrier_asyncAlthough the main process anddispatch_asyncExactly the same but cannot be applied to the global queue because the global queue does not have the v_table structure directly likedispatch_asyncexecute

To view_dispatch_continuation_invoke_inline:

Actually execute the block callback and see_dispatch_client_callout, in the above_dispatch_continuation_initTo wrap the block indx_pushWrapped in when calledqos.

We can also print the stack information to verify the process

Its block callback executes the call path as follows:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release

The bottom layer of asynchronous functions is more complexThere are a lot of branches, but the idea is relatively clear, use oneThe linked list holds all blocks, maintains a linked list in the queue itselfNew blockPut it in the back,First in first outAnd then in the underlying thread pool,Take out blocks in turnAnd executed.

4. To summarize

  • GCDDepending on queues, queues are divided intoSerial queuesandConcurrent queue, the main queue created by the system (the queue created by the system1) and global queues (the system creates queues4 to 15), the memory size of the queue is generally approximately64 * 1024Size.
  • There are 2 kinds of functions, there are 2 kinds of queues so they haveFour kinds of combinationThe big direction, the synchronization function willjamQueues, asynchronous functions don’t, serial queuesFIFOFirst in, first out, concurrent queue is okPerform multipleTasks, no need to wait.
  • Deadlocks are essentiallyThe queue in which the thread executes the block callbackAnd in theWaiting queueisSame queueCause the deadlock to crash.
  • Queues are essentiallydispatch_queue_tStructure object that we definedlable.priority,specificAs well asisa.Reference countingThere are approximately 2 steps to create a queue. The first step is:Normalized parameter.Processing queue priorityTo supportovercommitWhen the queue is createdWhether or not the system has enough resourcesWill restart a thread; Step 2: Thread name, type processing,Thread initialization, queue processing.
  • Synchronization function principle main 3 steps, taskjoinThe queue,performTask callback, taskBreak the ranks.
  • Asynchronous function principle: the queue itself uses oneThe linked list holds all tasksBlock, followFirst in first outAnd then in the underlying thread poolRemove the blocks one by one.