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_inline
In the functionSign breakpoints in engineering these 3 methodsDiscovery comes to this method in combination with the above BT call stack discovery_dispatch_sync_function_invoke
This 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_wakeup
Because 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_waiter
This 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_wakeup
There are four functions that return, so let’s make a symbolic breakpoint 在_dispatch_lane_class_barrier_complete
System-level functions are found in the We know about the global queue.dq_push = _dispatch_root_queue_push
We 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_push
After a complex logical calculation after the confluence to the global concurrent queue_dispatch_root_queue_push
here 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_init
When it comes to this_dispatch_root_queues_init
Insert 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_once
This function is only executed once, so_dispatch_root_queues_init_once
What did you do? We found it here_dispatch_worker_thread2
For ease of reading, put the previous asynchronous concurrent call stack againAnd you can see the next onelibsystem_pthread.dylib
The 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.