Welcome to the iOS Basics series (suggested in order)

IOS low-level – Alloc and init explore

IOS Bottom – Isa for everything

IOS low-level – Analysis of the nature of classes

IOS Underlying – cache_t Process analysis

IOS Low-level – Method lookup process analysis

IOS bottom layer – Analysis of message forwarding process

IOS Low-level – How does Dyld load app

IOS low-level – class load analysis

IOS low-level – Load analysis of categories

IOS Exploration – Principles related to multithreading

IOS Explorer – Multi-threaded GCD app

1. Write at the front

The purpose of this article is to fully grasp the usage skills of the upper API by analyzing the underlying implementation related to GCD.

2. Preliminary analysis

In general, the more convenient an API is, the less likely it is to see how it works, and the more likely it is to get into the habit of using it.

So what is the idea of analyzing the underlying source code of GCD? Where to start?

— — — — — — — — — — — — — — — — — — — — — — — — — –

Remember the core idea of the COMMUNIST Party:

Adds the task to the queue and specifies the function that executes the task

Analysis of the underlying source code of GCD is closely around this idea:

  • How is the queue created
  • The difference between synchronous and asynchronous functions
  • When the task was executed

3. Basic exploration

To study source code, you first need to find the library in which the source code resides.

The underlying layer of GCD is more esoteric than Objc because it has more branches, more macro definitions, longer names, fewer comments from the system, and a lot of kern-level operations involved, so analyzing GCD doesn’t explain it line by line, just pick out the key parts.

— — — — — — — — — — — — — — — — — — — — — — — — — –

The underlying code comes from Apple’s open source libraries

Libdispatch for Apple openSource

My Github is partially annotated and will be updated in the future

The underlying annotation github’s libDispatch

3.1 Creating queues

The exploration focuses on:

  • How is the queue created
  • How are serial and asynchronous distinguished

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Queue creation using dispatch_queue_CREATE is known, so you can place a dispatch_queue_CREATE symbol breakpoint in the project.

Display after running:

Dispatch_queue_create is in the libdispatch.dylib library and _dispatch_lane_create_with_target is called immediately.

Apple often uses the prefix "_" for the underlying functions, so that the underlying functions can be quickly adapted.Copy the code

Try searching for _dispatch_lane_create_with_target in libdispatch,

There are only three results, and the first is the function implementation.

  • 1.dqaiThe initialization
dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
Copy the code

When creating a queue, the queue type we specify is DQA (serial or concurrent). The incoming DQA gets a DQAI of type dispatch_queue_attr_info_t via the _dispatch_queue_attr_to_info function.

Dispatch_queue_attr_info_t is stored in bit-domain mode. Its structure is as follows:

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

And the _dispatch_queue_attr_to_info function is implemented as follows

1.1 initializes the empty struct. If DQA is empty, return the empty struct. Dqai is NULL for serial queue (serial NULL) and returns. 1.2 When DQA is not empty, the bit field in the structure body is assigned and dQAI is returned. Assigns a value to the bitfield in the body of a structure when it is a concurrent queue (concurrency is not NULL), and returns.Copy the code

The distinction between serial and concurrent is made in the first line of code, returneddqaiThe state of is the watershed between the two subsequent judgments.

  • 2.overcommitThe assignment of

Overcommit is used for subsequent calculation of the subscript value of the template array.

overcommit = dqai.dqai_concurrent ?
        _dispatch_queue_attr_overcommit_disabled :
        _dispatch_queue_attr_overcommit_enabled;
Copy the code

Determine the value of overcommit based on whether dqai.dqai_concurrent exists. The dQAI of the serial queue is empty, so

Queue type overcommit
serial _dispatch_queue_attr_overcommit_enabled
concurrent _dispatch_queue_attr_overcommit_disabled
  • (3) initializationtq(Target queue to execute block)
tq = _dispatch_get_root_queue(
        qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
        overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
Copy the code

Among them

Calculation method:

qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
Copy the code

Related macro definitions:

#define DISPATCH_QOS_UNSPECIFIED        ((dispatch_qos_t)0)
#define DISPATCH_QOS_DEFAULT            ((dispatch_qos_t)4)
typedef enum {
	_dispatch_queue_attr_overcommit_unspecified = 0,
	_dispatch_queue_attr_overcommit_enabled,
	_dispatch_queue_attr_overcommit_disabled,
} _dispatch_queue_attr_overcommit_t;
Copy the code

If the qos of DISPATCH_QOS_UNSPECIFIED is DISPATCH_QOS_DEFAULT, the default qos of DISPATCH_QOS_DEFAULT is set to the default qos of DISPATCH_QOS_DEFAULT. Otherwise, the qos of DISPATCH_QOS_DEFAULT is set to the default qos. Is equal to 4;

Overcommit The previous assignment was overcommitted, which is an enumerated value with string behavior 1(_dispatch_queue_attr_overcommit_enabled) and concurrency 2(_dispatch_queue_attr_overcommit_disabled).

The value is then passed to the _dispatch_get_root_queue function for calculation

We can quickly calculate the value of 2 * (qos-1) + overcommit:

Queue type 2 * (qos – 1) + overcommit
serial 7
concurrent 8

The final value has been obtained, so make a mark and go back to analyze the meaning of the value later.

  • (4) setvtable

The concurrency is queue_concurrent, the string behavior is queue_serial, and vtable is the identifier used to concatenate strings during subsequent queue initialization.

#define DISPATCH_CLASS_SYMBOL(name) _dispatch_##name##_vtable
Copy the code

In GCD, many macro definitions use the ## parameter ##, which means string concatenation.

  • ⑤ Allocate space and initialize queues

Vtable alloc the space of the corresponding class according to whether it is serial or concurrent

There is a trick to searching the underlying code. A direct search for _dispatch_object_alloc is more associated and can be optimized according to its parameter type. Since the first argument is const void *, we can search for _dispatch_object_alloc directly (const void * reduces the scope).Copy the code

The _dispatch_object_alloc function will follow along

static inline id _os_objc_alloc(Class cls, size_t size){ id obj; size -= sizeof(((struct _os_object_s *)NULL)->os_obj_isa); while (unlikely(! (obj = class_createInstance(cls, size)))) { _dispatch_temporary_resource_shortage(); } return obj; }Copy the code

It can be seen that the Vtable is finally converted into the corresponding CLS after splicing.

Objc dispatch_object objC dispatch_object objC dispatch_object

Search dispatch_object,

Seeing the dispatch_object_t type, continue searching for dispatch_object_t:

typedef union {
	struct _os_object_s *_os_obj;
	struct dispatch_object_s *_do;
	struct dispatch_queue_s *_dq;
	struct dispatch_queue_attr_s *_dqa;
	struct dispatch_group_s *_dg;
	struct dispatch_source_s *_ds;
	struct dispatch_mach_s *_dm;
	struct dispatch_mach_msg_s *_dmsg;
	struct dispatch_semaphore_s *_dsema;
	struct dispatch_data_s *_ddata;
	struct dispatch_io_s *_dchannel;
} dispatch_object_t DISPATCH_TRANSPARENT_UNION;
Copy the code

Dispatch_object_t isa union, and the union form is also present in isa.

It contains a number of common GCD types such as dispatch_group_s, dispatch_source_s, dispatch_semaphore_s, and dispatch_queue_s, which are all dispatch_object_t types.

typedef struct dispatch_object_s {
private:
	dispatch_object_s();
	~dispatch_object_s();
	dispatch_object_s(const dispatch_object_s &);
	void operator=(const dispatch_object_s &);
} *dispatch_object_t;
Copy the code

*dispatch_object_t is used to wrap dispatch_object_s.

This design pattern is similar to declaring a base class and then inheriting it from one base class to the next. However, using base classes can lead to later classes becoming larger and harder to maintain, which is avoided in the union form.

1.2 Initialize dq using the _dispatch_queue_init constructor with the third argument width and the following macro definitions:

#define DISPATCH_QUEUE_WIDTH_FULL			0x1000ull
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
Copy the code
Queue type width
serial 1
concurrent DISPATCH_QUEUE_WIDTH_MAX

Width indicates the maximum number of tasks that can be executed at the same time. DISPATCH_QUEUE_WIDTH_POOL explains later.

1.3 Setting attributes of queue DQ The target queue targetQ is TQ

dq->do_targetq = tq;
Copy the code

Thus, dq and TQ are not the same thing, dQ is the final initialization object, and TQ is the queue that executes the block code. Tq is one of the properties of DQ.


At this point, the attributes of the queue are assigned, the target queue is set, and the initialization is complete.

In a moment of joy, consider:

How is tQ allocated? Does it need to be initialized every time?

The creation of the TQ is not mentioned in the above analysis. Let’s go back to where we started and see how threads are created, okay?

dispatch_queue_t queue1 = dispatch_queue_create("juejin", DISPATCH_QUEUE_CONCURRENT);
    
dispatch_queue_t queue2 = dispatch_queue_create("juejin", NULL);
Copy the code

Print two queues respectively:

Queue type width target
serial 0x1 com.apple.root.default-qos
concurrent 0xffe com.apple.root.default-qos.overcommit

About width in the _dispatch_queue_init constructor:

The serial queue is 0x1, the concurrent queue is 0xffe, and there is a macro defined DISPATCH_QUEUE_WIDTH_POOL(0xFFf),

Under the search DISPATCH_QUEUE_WIDTH_POOL

static const struct dispatch_queue_global_s _dispatch_custom_workloop_root_queue = {
	DISPATCH_GLOBAL_OBJECT_HEADER(queue_global),
	.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE,
	.do_ctxt = NULL,
	.dq_label = "com.apple.root.workloop-custom",
	.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
	.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
			DISPATCH_PRIORITY_SATURATED_OVERRIDE,
	.dq_serialnum = DISPATCH_QUEUE_SERIAL_NUMBER_WLF,
	.dgq_thread_pool_size = 1,
};
Copy the code

DISPATCH_QUEUE_WIDTH_POOL is used by global queues.

The target of dQ is TQ. How is tQ created?

Return to the value of 2 * (qos-1) + overcommit marked above,

tq = _dispatch_get_root_queue(
        qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos, 
        overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq; 
Copy the code

_dispatch_get_root_queue Internally obtains the corresponding TQ from the array template.

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

Queues [] _dispatch_root_queues[]

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

The serial and concurrent subscripts 7 and 8 are calculated to match com.apple.root.default-qos and com.apple.root.default-qos. Overcommit, which match the printed values.

Customizable serial and concurrent subscripts vary by overcommit.

Therefore, TQ is obtained directly by subscripting the template array based on serial and concurrent values, and does not need to be created each time to improve performance.

So, when was the template array created?

As you can imagine, the template array must be initialized with libDispatch initialization.

Initialize the template array

DISPATCH_ROOT_QUEUE_COUNT in the for loop is a multi-level nested macro definition:

#define DISPATCH_ROOT_QUEUE_COUNT   (DISPATCH_QOS_NBUCKETS * 2)
#define DISPATCH_QOS_NBUCKETS       (DISPATCH_QOS_MAX - DISPATCH_QOS_MIN + 1)
#define DISPATCH_QOS_MIN            DISPATCH_QOS_MAINTENANCE
#define DISPATCH_QOS_MAX            DISPATCH_QOS_USER_INTERACTIVE
Copy the code

After a macro definition, DISPATCH_ROOT_QUEUE_COUNT is 12. The template in _dispatch_root_queues[] has dq_serialnum of 4-15(missing 1, 2, and 3), which is exactly 12 templates.

② Initialize the primary queue and global queue

Search for _dispatch_main_q and _dispatch_mgr_q,

_dispatch_MAIN_q is the primary queue. _dispatch_mgr_q dq_label for com. Apple. Root. Libdispatch – the queue manager, _dispatch_mgr_root_queue do_targetq orientation, it is the global queue. Dq_serialnum is 1, 2, and 3(to fill in the gap before the minimum value of 4 in their template array).

The primary queue and global queue are also initialized when libDispatch is initialized, which is why they can be used globally directly.

Look at the implementation of dispatch_get_MAIN_queue, _dispatch_MAIN_q at a glance.

Summary of queue creation:

  • The parameters of the custom queue pass are determineddqai.dqaiDetermines whether the queue is serial or concurrent
  • Custom queueTarget queue (TQ)Is created quickly using an array of templates
  • Global queues, main queues, and template arrays are accompanimentslibdispatchThe initialization of the

3.2 Task Execution

The exploration focuses on:

  • blockBoth need to be called manually, andGCDThe task does not show the call, why is it executed

— — — — — — — — — — — — — — — — — — —

Make a breakpoint inside the task to see the stack information,

The _dispatch_client_callout on the stack is abnormal

static inline void
_dispatch_client_callout(void *ctxt, dispatch_function_t f)
{
	return f(ctxt);
}
Copy the code

GCD wraps the task as a parameter f of type dispatch_function_t, and when appropriate the system calls _dispatch_client_callout to perform the task without external display calls.

3.3 Synchronization Function

The exploration focuses on:

  • Why do synchronization functions perform tasks sequentially
  • Possible problems with the use of synchronization functions, such as how deadlocks occur

— — — — — — — — — — — — — — — — — — —

Synchronous dispatch_sync is familiar to us. Direct search, keep jumping

Dispatch_sync -> _dispatch_sync_f -> _dispatch_sync_f_inline,

The func parameter is a note of caution: the task is wrapped as dispatch_function_t and called where appropriate.

If dq_width is equal to 1, if it is equal to the serial queue, go _dispatch_barrier_sync_f, method name has very familiar fence function, which also shows from the side that the fence function and synchronous serial is very similar.

By the way, recall the stack information when a deadlock occurs,

Serial queue exploration

_dispatch_sync_F_slow = dispatch_sync_f_slow

The _dispatch_barrier_SYNc_f_inline method internally obtains the thread ID with _dispatch_tid_self and then _dispatch_queue_trY_acquire_BARRIer_sync determines whether the thread is waiting. If you wait to enter _dispatch_SYNc_F_SLOW, otherwise the _dispatch_lane_barrier_sync_INVOke_AND_complete call will eventually come to _dispatch_client_callout to perform the task.

So if we go to _dispatch_sync_f_slow,

_dispatch_trace_ITEM_push is called internally to queue tasks in the form of push. The queue is FIFO, so the sequential structure is realized.

Then __DISPATCH_WAIT_FOR_QUEUE__,

Call _dispatch_WAIT_prepare to get the queue task state from the bottom of the OS, and then pass in the state of the task and the state of the task.

_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
	return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
Copy the code

Both perform xOR operations. If they are the same, they are both waiting and the system throws a deadlock.

If not, it just waits unilaterally, recursively executing the task by calling _dispatch_sync_INVOke_and_COMPLEte_RECURse. The reason for recursion is that the scheduling of tasks is the low-level behavior of the system and does not know when the wait will end.

GCDThere are a lot of calls underneathosThe underlying reason for getting state callbacks is that task execution depends on thread state, and thread scheduling is underlying system behavior

Concurrent queue exploration

(2) If the dq_width is not equal to 1, that is, the concurrent queue, call _dispatch_sync_INVOke_and_complete directly to execute the task.

Internal call _dispatch_sync_function_INVOke_inline, then a task _dispatch_thread_frame_push, _dispatch_client_callout, Execute _dispatch_thread_frame_POP once. The sequential execution structure is also implemented.

Conclusion:

  • The task of the synchronization function is adoptedpushThe team,popThe way out of the team to achieve the sequential execution
  • The synchronization function occurs when a mutual wait is generatedA deadlock
  • The synchronization function is similar to the fence function

3.4 Asynchronous Functions

The exploration focuses on:

  • How do asynchronous functions create threads
  • When the task of an asynchronous function is wrapped

— — — — — — — — — — — — — — — — — — —

Same idea, just search dispatch_async(dis,

void dispatch_async(dispatch_queue_t dq, Dispatch_block_t work) {queue.dispatch_continuation_t dc = _dispatch_continuation_alloc(); uintptr_t dc_flags = DC_FLAG_CONSUME; dispatch_qos_t qos; // Wrap the task and return the corresponding priority qos = _dispatch_continuation_init(dc, Dq, work, 0, dc_FLAGS); _dispatch_continuation_async(dq, dc, qos, dc->dc_flags); }Copy the code
  • See first_dispatch_continuation_initTo jump to_dispatch_continuation_init_f.

Functions wrap tasks and their associated properties inside.

  • Looking at the_dispatch_continuation_async.

Dx_push is a macro definition that calls dq_push,

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

Dq_push has a lot of assignments,

Explore _dispatch_root_queue_push directly, because others will eventually come to _dispatch_root_queue_push, focusing only on the points you want to explore and not too much on the side sections.

The difference between the parameters of ① and ② lies in qos.

It is known that asynchronous serial executes tasks sequentially and asynchronous concurrent executes tasks out of order. Qos affects the priority of task scheduling. So we can get: ① is the concurrent branch, ② is the serial branch.Copy the code

There are branches here, but if you look at the concurrent branches,

After simple processing, we go back to the _dispatch_root_queue_PUSH_inline in the serial branch, so whether serial or concurrent, we end up at _dispatch_root_queue_PUSH_inline. It is also understandable that there are more concurrent processing steps than serial ones.

Jump: _dispatch_root_queue_PUSH_inline -> _dispatch_root_queue_POKE -> _dispatch_root_queue_POke_slow

Can_request: indicates the number of threads that can be requested. T_count: indicates the size of the thread pool. Remaining: indicates the remaining threadsCopy the code

Two layers of DO -whild loops are used internally. A two-tier loop dynamically maintains thread pool capacity.

The first layer determines and controls the state of the thread pool. Each request to remaining = CAN_REQUEST is not processed when the thread pool is full.

The second layer uses pthread_create to create threads, and the request goes to — Remaining, reducing the number of remaining threads by one.

Conclusion:

  • Asynchronous function usepthread_createCreate a thread
  • The tasks of asynchronous functions are wrapped before the thread is created, waiting to be scheduled by the thread

3.5 the singleton

Skip to dispatch_once -> dispatch_once_f

(1) The onceToken parameter is converted to a variable of type L of dispatch_once_gate_T. L is the underlying atomic operation parameter. The value v is obtained by running os_atomic_LOAD.

(2) If the status is DLOCK_ONCE_DONE, the task has been executed.

③ If the task has not been executed at this time, it will be locked by atomic operation at the bottom, in order to ensure the uniqueness of the current task execution. After the callback function is locked, the current task is unlocked and the current task status is set to DLOCK_ONCE_DONE.

④ If other tasks call _dispatch_once_WAIT for an infinite number of times during task execution, the current task has obtained the lock and other tasks cannot obtain the lock.

static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
		dispatch_function_t func){
	_dispatch_client_callout(ctxt, func);
	_dispatch_once_gate_broadcast(l);
}
Copy the code

_dispatch_once_callout Calls _dispatch_client_callout to perform tasks, and then calls _dispatch_once_gate_broadcast to broadcast the task status.

3.6 a semaphore

Semaphore_create, dispatch_semaphore_WAIT, dispatch_semaphore_signal

dispatch_semaphore_t
dispatch_semaphore_create(long value)
{
	dispatch_semaphore_t dsema;
	if (value < 0) {
		return DISPATCH_BAD_INPUT;
	}
_dispatch_object_alloc(DISPATCH_VTABLE(semaphore),
			sizeof(struct dispatch_semaphore_s));
	dsema->do_next = DISPATCH_OBJECT_LISTLESS;
	dsema->do_targetq = _dispatch_get_default_queue(false);
	dsema->dsema_value = value;
	_dispatch_sema4_init(&dsema->dsema_sema, _DSEMA4_POLICY_FIFO);
	dsema->dsema_orig = value;
	return dsema;
}
Copy the code

Output a semaphore of type dispatch_semaphoRE_t with a value of value based on the parameters passed in. Note that passing a semaphore starting value less than 0 causes NULL to be returned.

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
	long value = os_atomic_dec2o(dsema, dsema_value, acquire);
	if (likely(value >= 0)) {
		return 0;
	}
	return _dispatch_semaphore_wait_slow(dsema, timeout);
}
Copy the code

An internal call to OS_Atomic_DEC2O reduces the value of the passed semaphore Dsema by 1. The underlying symbol in os_ATOMIC_DEC2O is “-“.

_os_atomic_c11_op((p), (v), m, sub, -)
Copy the code

If value is greater than or equal to 0(indicating that the semaphore is greater than or equal to 1), the thread on which the function is located continues to execute the following statement; Otherwise, the function blocks the current thread.

If the value of the waiting semaphore is incremented by one by the dispatch_semaphore_signal function and the thread on which the function is located obtains the semaphore, proceed down and decrement the semaphore by one.

If the semaphore is never retrieved, the thread will automatically execute subsequent statements when the timeout period expires.

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema){
	long value = os_atomic_inc2o(dsema, dsema_value, release);
	if (likely(value > 0)) {
		return 0;
	}
	if (unlikely(value == LONG_MIN)) {
		DISPATCH_CLIENT_CRASH(value,
				"Unbalanced call to dispatch_semaphore_signal()");
	}
	return _dispatch_semaphore_signal_slow(dsema);
}
Copy the code

An internal call to OS_atomic_INC2O increses the semaphore by 1. Os_atomic_inc2o The underlying symbol is “+”.

_os_atomic_c11_op_orig((p), (v), m, add, +)
Copy the code

A value less than or equal to 0 (indicating a negative semaphore) indicates that there are no threads currently processing subsequent statements.

When value is greater than 0, it indicates that there are threads (the number of threads equals value) that need to process subsequent statements, and the function wakes up a waiting thread, if the thread has a priority, the thread with the highest priority, otherwise it wakes up randomly.

3.7 scheduling group

dispatch_group_create:

dispatch_group_t
dispatch_group_create(void){
	return _dispatch_group_create_with_count(0);
}

static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n){
	dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
			sizeof(struct dispatch_group_s));
	dg->do_next = DISPATCH_OBJECT_LISTLESS;
	dg->do_targetq = _dispatch_get_default_queue(false);
	if (n) {
		os_atomic_store2o(dg, dg_bits,
				-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
		os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); 
	}
	return dg;
}
Copy the code

Create a scheduling group. Normally, the value is 0 and the OS does not save the value. If the parameter is not 0, the underlying value of the OS is 1.

Dispatch_group_enter:

void dispatch_group_enter(dispatch_group_t dg){ uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits, DISPATCH_GROUP_VALUE_INTERVAL, acquire); uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK; . }Copy the code

Call OS_atomic_sub_ORIG2O to decrease the task count value by 1

dispatch_group_leave:

1. Increase the os_ATOMic_ADD_ORIG2O task count by 1

② _dispatch_group_WAKE calls _dispatch_continuation_async(asynchronous function analysis) to execute tasks asynchronously

③ If the leave times are more than enter times, the program crashes

dispatch_group_async:

dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
		dispatch_block_t db)
{
	dispatch_continuation_t dc = _dispatch_continuation_alloc();
	uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
	dispatch_qos_t qos;

	qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
	_dispatch_continuation_group_async(dg, dq, dc, qos);
}
Copy the code

Similar to asynchronous functions, _dispatch_continuation_init saves tasks, and _dispatch_continuation_group_async asynchronizes tasks

But, you know,

Asynchronous task execution is also a task for the group, which also requires Enter. After executing the task, leave will also be called once.

4. Write in the back

The above is a very superficial analysis of the GCD underlying layer, which is already somewhat obscure, and there are OS underlying layer, POSIX underlying layer, etc.. It’s a big head to think about.

However, exploring the source code is a head-bald process, in other words, the process of becoming stronger.

No matter when you start, the more important is that do not stop after starting.

Block, lock, memory management and other basic principles will be updated in the future, please pay attention.