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

Synchronization function deadlock

The main thread is waiting to execute the task first because you synchronize the function

The main queue waits for the main thread’s task to complete before executing its own task. The waiting between the main queue and the main thread causes a deadlock

- (void)textDemo2{
    // 
    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // Asynchronous function
    dispatch_async(queue, ^{
        NSLog(@"2");
        // The serial queue and synchronization functions can cause a deadlock problem
        dispatch_sync(queueThe ^ {}); }); NSLog(@"5");
}
Copy the code

After the deadlock is caused above, BT looks at the call stackLet’s follow the source code to analyze:

void
dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
{
	uintptr_t dc_flags = DC_FLAG_BLOCK;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
	}
	_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
Copy the code

This is a familiar process, as we mentioned in the last article, _dispatch_sync_f -> _dispatch_sync_f_inline. In this function, we know that the dq_width of the synchronization function is 1

if (likely(dq->dq_width == 1)) {
		return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
}
Copy the code

So we’ve got _dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline and if we look at the deadlock stack above, There’s a _dispatch_sync_f_slow, okay so we’re going to go to _dispatch_barrier_sync_f_inline and we’re going to go to _dispatch_sync_f_slow and we’re going to find the function __DISPATCH_WAIT_FOR when an error occurs _QUEUE__

static void
__DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq)
{
	uint64_t dq_state = _dispatch_wait_prepare(dq);
    // The second argument. Dsc_waiter = _dispatch_tid_self(), which is the thread ID
	if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
		DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
				"dispatch_sync called on queue "
				"already owned by current thread"); }}Copy the code

_dq_state_drain_locked_BY (dq_state, DSC ->dsc_waiter) The second parameter here. Dsc_waiter = _dispatch_tid_self() is the thread ID

#define _dispatch_tid_self()		((dispatch_tid)_dispatch_thread_port())
Copy the code

_dq_state_drain_locked_by -> _dispatch_lock_is_locked_by

static inline bool
_dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid)
{
	// equivalent to _dispatch_lock_owner(lock_value) == tid
	return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}
Copy the code

DLOCK_OWNER_MASK is a maximum, which means that this is not true as long as lock_value ^ tid is not 0. On the other hand, lock_value ^ tid = 0 so when are the two values of Xor equal to 0? Of course, the answer is equal. If the tid of the waiting thread is the same as the TID of the thread that is about to be called, a deadlock will occur

Sync function + global queue

dispatch_sync + global_queue

   dispatch_queue_t queue1 = dispatch_get_global_queue(0.0);
   dispatch_sync(queue1, ^{
        NSLog(@ "% @"[NSThread currentThread]);
   });
Copy the code

And at the same time btWe can put a sign break point on the third line, at_dispatch_sync_f_inlineIn the functionSign breakpoints in engineering these 3 methodsDiscovery comes to this method in combination with the above BT call stack discovery_dispatch_sync_function_invokeThis way. The rest of the process is simpler._dispatch_sync_function_invoke -> _dispatch_sync_function_invoke_inline ->_dispatch_client_callout

Asynchronous functions + global queues/concurrent queues

dispatch_async + DISPATCH_QUEUE_CONCURRENT dispatch_async + global_queue

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);
	_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
Copy the code

Dispatch_async -> _dispatch_continuation_async -> dX_push This process is familiar to the previous article. .dq_push = _dispatch_root_queue_push

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
	.do_type        = DISPATCH_QUEUE_CONCURRENT_TYPE,
	.do_dispose     = _dispatch_lane_dispose,
	.do_debug       = _dispatch_queue_debug,
	.do_invoke      = _dispatch_lane_invoke,

	.dq_activate    = _dispatch_lane_activate,
	.dq_wakeup      = _dispatch_lane_wakeup,
	.dq_push        = _dispatch_lane_concurrent_push,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
	.do_type        = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
	.do_dispose     = _dispatch_object_no_dispose,
	.do_debug       = _dispatch_queue_debug,
	.do_invoke      = _dispatch_object_no_invoke,

	.dq_activate    = _dispatch_queue_no_activate,
	.dq_wakeup      = _dispatch_root_queue_wakeup,
	.dq_push        = _dispatch_root_queue_push,
);
Copy the code

The global queue corresponds to _dispatch_root_queue_push, the concurrent queue corresponds to dispatch_lane_concurrent_push, and the global queue corresponds to _dispatch_root_queue_push. Let’s start with the more complicated concurrent queues

void
_dispatch_lane_concurrent_push(dispatch_lane_t dq, dispatch_object_t dou,
		dispatch_qos_t qos)
{
	// <rdar://problem/24738102&24743140> reserving non barrier width
	// doesn't fail if only the ENQUEUED bit is set (unlike its barrier
	// width equivalent), so we have to check that this thread hasn't
	// enqueued anything ahead of this call or we can break ordering
	if (dq->dq_items_tail == NULL&&! _dispatch_object_is_waiter(dou) && ! _dispatch_object_is_barrier(dou) && _dispatch_queue_try_acquire_async(dq)) {return _dispatch_continuation_redirect_push(dq, dou, qos);
	}

	_dispatch_lane_push(dq, dou, qos);
}
Copy the code

Dispatch_lane_push this is actually a serial queue. Dq_push

void
_dispatch_lane_push(dispatch_lane_t dq, dispatch_object_t dou,
		dispatch_qos_t qos)
{
	/ /...
	if (unlikely(_dispatch_object_is_waiter(dou))) {
		return _dispatch_lane_push_waiter(dq, dou._dsc, qos);
	}

	/ /...
	if (flags) {
		returndx_wakeup(dq, qos, flags); }}Copy the code

And you can see that there are only two returns in there, so in the demo, I’ll do breakpoints. Dx_wakeup global search is located to a macro

#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
Copy the code

searchdq_wakeupBecause we’re looking at concurrent queues.dq_wakeup = _dispatch_lane_wakeup, You can tell by the sign break point that it didn’t go_dispatch_lane_push_waiterThis method, so the asynchronous concurrent process link should be:dispatch_async -> _dispatch_continuation_async -> dx_push -> _dispatch_lane_concurrent_push -> _dispatch_lane_push -> dx_wakeup -> _dispatch_lane_wakeup -> _dispatch_queue_wakeup_dispatch_queue_wakeupThere are four functions that return, so let’s make a symbolic breakpoint_dispatch_lane_class_barrier_completeSystem-level functions are found in the We know about the global queue.dq_push = _dispatch_root_queue_pushWe tried to make a break point and found that we did. So it can be summed up this way: concurrent queues created by manual systems_dispatch_lane_concurrent_pushAfter a complex logical calculation after the confluence to the global concurrent queue_dispatch_root_queue_pushhere This also conveniently verifies that the global queue is a special kind of concurrent queue._dispatch_root_queue_push_inline -> _dispatch_root_queue_poke -> _dispatch_root_queue_poke_slow -> _dispatch_root_queues_initWhen it comes to this_dispatch_root_queues_initInsert another singleton as appropriate

Singletons underlying principle

static inline void
_dispatch_root_queues_init(void)
{
	dispatch_once_f(&_dispatch_root_queues_pred, NULL,
			_dispatch_root_queues_init_once);
}
Copy the code

So this is a singleton, we call dispatch_once from the top and dispatch_once_f from the bottom

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
	dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
Copy the code
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
	dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if! DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
	if (likely(v == DLOCK_ONCE_DONE)) {
		return;
	}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	if (likely(DISPATCH_ONCE_IS_GEN(v))) {
		return _dispatch_once_mark_done_if_quiesced(l, v);
	}
#endif
#endif
	if (_dispatch_once_gate_tryenter(l)) {
		return _dispatch_once_callout(l, ctxt, func);
	}
	return _dispatch_once_wait(l);
}
Copy the code

So the first time it’s called, it’s going to go to _dispatch_once_gate_tryEnter (l), _dispatch_once_gate_tryenter and do some atomic operation and lock the current thread, So singletons are also thread safe _dispatch_lock_value_for_self(),_dispatch_once_callout -> _dispatch_once_gate_broadcast -> _dispatch_once_mark_done

static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
	return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
Copy the code

V == DLOCK_ONCE_DONE; v == DLOCK_ONCE_DONE

Asynchronous functions + global queues/concurrent queues

So let’s go back to the above process_dispatch_root_queues_init_onceThis function is only executed once, so_dispatch_root_queues_init_onceWhat did you do? We found it here_dispatch_worker_thread2For ease of reading, put the previous asynchronous concurrent call stack againAnd you can see the next onelibsystem_pthread.dylibThe underlying API, i.e., GCD, is a change encapsulated on top of pThread. Let’s go back to our previous function

static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{
	// We know that n=1 and floor=0 from the arguments passed outside
    int remaining = n;
	_dispatch_root_queues_init();
 	// ...
	int can_request, t_count;
	t_count = os_atomic_load2o(dq, dgq_thread_pool_size, ordered);
	do {
        // The number of remaining threads available
		can_request = t_count < floor ? 0 : t_count - floor;
        // If the required quantity is greater than the remaining quantity, an exception is reported
		if (remaining > can_request) {
			_dispatch_root_queue_debug("pthread pool reducing request from %d to %d",
					remaining, can_request);
			os_atomic_sub2o(dq, dgq_pending, remaining - can_request, relaxed);
			remaining = can_request;
		}
		if (remaining == 0) {
			_dispatch_root_queue_debug("pthread pool is full for root queue: "
					"%p", dq);
			return; }}while(! os_atomic_cmpxchgv2o(dq, dgq_thread_pool_size, t_count, t_count - remaining, &t_count, acquire));// ...
}
Copy the code

Here the do whlie loop dgq_thread_pool_size is equal to 1, compared to the number of threads mentioned in the previous article

  _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
      DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
      (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0))
Copy the code
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
#define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
Copy the code

So DISPATCH_QUEUE_WIDTH_POOL here – DISPATCH_QUEUE_WIDTH_MAX = 1

struct dispatch_queue_global_s _dispatch_mgr_root_queue = {
	.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
	.dgq_thread_pool_size = 1};Copy the code

So it follows from this: Dq_atomic_flags =1 global concurrent queue. Dgq_thread_pool_size =1, dq_dispatch_queue_init Dgq_thread_pool_size =0 So how many threads can you create?

Dgq_thread_pool_size Thread pool size

static inline void
_dispatch_root_queue_init_pthread_pool(dispatch_queue_global_t dq,
		int pool_size, dispatch_priority_t pri)
{ 
    / /...
	int thread_pool_size = DISPATCH_WORKQ_MAX_PTHREAD_COUNT;
	dq->dgq_thread_pool_size = thread_pool_size;
}
Copy the code
#define DISPATCH_WORKQ_MAX_PTHREAD_COUNT 255
Copy the code

The maximum number of threads in the pool is 255. Look for the pthread description in the official apple documentation. The size of the thread pool is 512kb, but the minimum size is 16kbWe know that the stack has a certain amount of memory, and the more space a thread takes up the less space it can open up, so the number of open threads is the highest when the thread size is 16KB. If all 1GB of kernel mode is used to open threads, then the amount is

  • Max. 1024 x 1024/16 = 64 x 1024
  • Minimum: 1024 x 1024/512 = 2048

So the number of threads is not fixed.