上篇文章 GCD底層分析(一) 分析了函數(shù)、隊列的創(chuàng)建、以及同步和異步函數(shù)的調(diào)用流程。
那么有以下疑問:
- 死鎖是怎么造成的?
- 同步和異步函數(shù)能否開辟線程?
這篇文章將著重分析這幾個問題。
一、死鎖的本質(zhì)
1.1 調(diào)用邏輯分析
在源碼中同步函數(shù)串行隊列會調(diào)用到_dispatch_sync_f_inline的邏輯:

_dispatch_barrier_sync_f會調(diào)用到_dispatch_barrier_sync_f_inline:

- 死鎖會走
_dispatch_sync_f_slow的邏輯。
1.2 代碼定位死鎖
有如下代碼:
- (void)test{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"test");
});
// dispatch_queue_t queue = dispatch_queue_create("com.HotpotCat.zai", DISPATCH_QUEUE_SERIAL);
// NSLog(@"1");
// dispatch_async(queue, ^{
// NSLog(@"2");
// dispatch_sync(queue, ^{
// NSLog(@"3");
// });
// });
// NSLog(@"5");
}

- 無論是否主線程死鎖都走同樣的邏輯。
- 并不是在
_dispatch_sync_f_slow函數(shù)中報錯,而是在__DISPATCH_WAIT_FOR_QUEUE__:
image.png
_dispatch_sync_f_slow調(diào)用__DISPATCH_WAIT_FOR_QUEUE__報錯:

-
dsc_waiter是當前隊列線程線程id。
#define _dispatch_tid_self() ((dispatch_tid)_dispatch_thread_port())
1.3 DISPATCH_WAIT_FOR_QUEUE 分析死鎖
__DISPATCH_WAIT_FOR_QUEUE__中報錯判斷函數(shù)是_dq_state_drain_locked_by:

- 傳遞的參數(shù)是
dq_state(同步函數(shù)傳遞參數(shù)的隊列狀態(tài))以及dsc_waiter(同步函數(shù)所在隊列線程的id)。
_dq_state_drain_locked_by的實現(xiàn):
static inline bool
_dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid)
{
return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}
直接調(diào)用_dispatch_lock_is_locked_by:
#define DLOCK_OWNER_MASK ((dispatch_lock)0xfffffffc)
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;
}
- 只要
lock_value ^ tid不為空,& DLOCK_OWNER_MASK肯定不為0。 -
lock_value ^ tid為0,& DLOCK_OWNER_MASK值才為0。 - 意味著只有
lock_value與tid相同,值才為0。說明要等待的線程與要調(diào)起的線程是同一個,構(gòu)成了矛盾。
1.4小結(jié)
死鎖現(xiàn)象
- 線程因為同步函數(shù)的原因等著執(zhí)行任務。
- 串行隊列等著線程的任務執(zhí)行完畢再執(zhí)行自己的任務。
- 串行隊列和線程相互等待造成死鎖。
死鎖的本質(zhì)
要等待的線程與要調(diào)起的線程是同一個,構(gòu)成了矛盾。(隊列調(diào)用dispatch_sync,但是隊列已經(jīng)被當前線程持有了。)
死鎖的條件
- 串行隊列(
queue)。 - 在
queue中調(diào)用dispatch_sync同步函數(shù)添加任務到queue。
也就是
test中的測試代碼,主線程和自定義串行隊列的例子。
二、單例的底層原理(dispatch_once)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"test");
});
2.1 dispatch_once
dispatch_once源碼實現(xiàn)如下:
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
-
val是全局靜態(tài)變量onceToken(dispatch_once_t)。
dispatch_once_f源碼如下:
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
//val標記 強轉(zhuǎn)(??)
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);
//已經(jīng)執(zhí)行過直接return
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
//標記為Done
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
//沒有被鎖鎖住
if (_dispatch_once_gate_tryenter(l)) {
//執(zhí)行任務
return _dispatch_once_callout(l, ctxt, func);
}
//沒有標記執(zhí)行過,并且被鎖鎖住了走這里的邏輯。等待開鎖。
return _dispatch_once_wait(l);
}
- 根據(jù)
val判斷是否已經(jīng)執(zhí)行過,執(zhí)行過直接返回。 - 執(zhí)行過但是沒有標記,直接進行標記返回
_dispatch_once_mark_done_if_quiesced。 - 沒有被鎖住執(zhí)行任務
_dispatch_once_callout。 - 沒有標記執(zhí)行過,并且被鎖鎖住了
_dispatch_once_wait等待開鎖。 - 線程加鎖說明了單例是線程安全的。
2.2 _dispatch_once_mark_done_if_quiesced
_dispatch_once_mark_done_if_quiesced:
static inline void
_dispatch_once_mark_done_if_quiesced(dispatch_once_gate_t dgo, uintptr_t gen)
{
if (_dispatch_once_generation() - gen >= DISPATCH_ONCE_GEN_SAFE_DELTA) {
//標記為Done
os_atomic_store(&dgo->dgo_once, DLOCK_ONCE_DONE, relaxed);
}
}
- 標記為
done。
2.3 _dispatch_once_gate_tryenter
_dispatch_once_gate_tryenter:
static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
//原子操作,線程鎖是 _dispatch_lock_value_for_self。判斷是否沒有加鎖
//os_atomic_cmpxchg 調(diào)用的是 __c11_atomic_compare_exchange_strong(原子方式運行)
return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
- 原子操作,判斷有沒有被鎖。
2.4 _dispatch_once_callout
_dispatch_once_callout執(zhí)行任務:
static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
dispatch_function_t func)
{
//block回調(diào)
_dispatch_client_callout(ctxt, func);
_dispatch_once_gate_broadcast(l);
}
- 調(diào)用
block回調(diào)。 -
_dispatch_once_gate_broadcast進行廣播:
static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
dispatch_lock value_self = _dispatch_lock_value_for_self();
uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
v = _dispatch_once_mark_quiescing(l);
#else
//設置狀態(tài)
v = _dispatch_once_mark_done(l);
#endif
if (likely((dispatch_lock)v == value_self)) return;
_dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v);
}
調(diào)用_dispatch_once_mark_done設置狀態(tài):
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
//先匹配,再設置狀態(tài)為Done。
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
- 這里與
_dispatch_once_mark_done_if_quiesced有點像。
單例底層調(diào)用的是dispatch_once_f,在_dispatch_root_queues_init中調(diào)用的就是這個函數(shù)。
三、GCD是如何創(chuàng)建線程的
上篇文章分析了dispatch_sync與dispatch_async的調(diào)用邏輯(文章的第六部分 GCD 函數(shù)調(diào)用流程 )。當時忽略了線程創(chuàng)建的問題。
根據(jù)分析的流程同步函數(shù)是直接執(zhí)行block回調(diào)的,不會開辟線程。那么異步函數(shù)是在什么時候開辟線程的呢?

- 主隊列在
App啟動main之后就創(chuàng)建好了。
3.1 全局隊列 & 并行隊列
3.1.1 源碼分析
根據(jù)上面的流程直接定位到_dispatch_root_queue_poke_slow:
static void
_dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor)
{
//_dispatch_root_queue_push_inline 傳遞 floor = 0
//_dispatch_root_queue_push_override 傳遞 n 為 1
int remaining = n;//進來一次異步全局并發(fā)隊列創(chuàng)建一個線程。相當于循環(huán)一次創(chuàng)建一個。
#if !defined(_WIN32)
int r = ENOSYS;
#endif
//單例,調(diào)用一次。設置 _dispatch_worker_thread2 給pthread
_dispatch_root_queues_init();
_dispatch_debug_root_queue(dq, __func__);
_dispatch_trace_runtime_event(worker_request, dq, (uint64_t)n);
#if !DISPATCH_USE_INTERNAL_WORKQUEUE
#if DISPATCH_USE_PTHREAD_ROOT_QUEUES
//全局并發(fā),自定義并發(fā)都走這里
if (dx_type(dq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE)
#endif
{
_dispatch_root_queue_debug("requesting new worker thread for global "
"queue: %p", dq);
//創(chuàng)建線程
r = _pthread_workqueue_addthreads(remaining,
_dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));
(void)dispatch_assume_zero(r);
return;
}
#endif // !DISPATCH_USE_INTERNAL_WORKQUEUE
#if DISPATCH_USE_PTHREAD_POOL
dispatch_pthread_root_queue_context_t pqc = dq->do_ctxt;
if (likely(pqc->dpq_thread_mediator.do_vtable)) {
while (dispatch_semaphore_signal(&pqc->dpq_thread_mediator)) {
_dispatch_root_queue_debug("signaled sleeping worker for "
"global queue: %p", dq);
if (!--remaining) {//--remaining 為 0 的時候返回。
return;
}
}
}
bool overcommit = dq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
if (overcommit) {
os_atomic_add2o(dq, dgq_pending, remaining, relaxed);
} else {
if (!os_atomic_cmpxchg2o(dq, dgq_pending, 0, remaining, relaxed)) {
_dispatch_root_queue_debug("worker thread request still pending for "
"global queue: %p", dq);
return;
}
}
int can_request, t_count;
// seq_cst with atomic store to tail <rdar://problem/16932833>
//加載 t_count = 能夠創(chuàng)建的線程數(shù)量
t_count = os_atomic_load2o(dq, dgq_thread_pool_size, ordered);
/*
//全局并發(fā)比自定義并發(fā) DFQ_WIDTH 大 1
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
//全局并發(fā)
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
//自定義并發(fā)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
*/
//普通的do-while循環(huán) 判斷 dgq_thread_pool_size 線程池大?。ㄈ植l(fā)從1開始,自定義并發(fā)從0開始)。由于大1,所以這里進行
do {
//floor為傳遞過來的,0 can_request = 能夠創(chuàng)建的數(shù)量
can_request = t_count < floor ? 0 : t_count - floor;
// remaining = 需要創(chuàng)建的數(shù)量(為n傳進來的參數(shù))
// 需要創(chuàng)建的數(shù)量大于能創(chuàng)建的?
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 修復為能提供的數(shù)量
remaining = can_request;
}
//修復后為0則表示已經(jīng)達到最大值了,不能再創(chuàng)建了。直接return
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));
#if !defined(_WIN32)
pthread_attr_t *attr = &pqc->dpq_thread_attr;
pthread_t tid, *pthr = &tid;
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
if (unlikely(dq == &_dispatch_mgr_root_queue)) {
pthr = _dispatch_mgr_root_queue_init();
}
#endif
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
//開辟線程
while ((r = pthread_create(pthr, attr, _dispatch_worker_thread, dq))) {
if (r != EAGAIN) {
(void)dispatch_assume_zero(r);
}
_dispatch_temporary_resource_shortage();
}
} while (--remaining);
#else // defined(_WIN32)
#if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
if (unlikely(dq == &_dispatch_mgr_root_queue)) {
_dispatch_mgr_root_queue_init();
}
#endif
do {
_dispatch_retain(dq); // released in _dispatch_worker_thread
#if DISPATCH_DEBUG
unsigned dwStackSize = 0;
#else
unsigned dwStackSize = 64 * 1024;//64M
#endif
uintptr_t hThread = 0;
while (!(hThread = _beginthreadex(NULL, dwStackSize, _dispatch_worker_thread_thunk, dq, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL))) {
if (errno != EAGAIN) {
(void)dispatch_assume(hThread);
}
_dispatch_temporary_resource_shortage();
}
#if DISPATCH_USE_PTHREAD_ROOT_QUEUES
if (_dispatch_mgr_sched.prio > _dispatch_mgr_sched.default_prio) {
(void)dispatch_assume_zero(SetThreadPriority((HANDLE)hThread, _dispatch_mgr_sched.prio) == TRUE);
}
#endif
CloseHandle((HANDLE)hThread);
} while (--remaining);
#endif // defined(_WIN32)
#else
(void)floor;
#endif // DISPATCH_USE_PTHREAD_POOL
}
- 在
_dispatch_root_queue_push_inline中傳遞的floor為0。 - 在
_dispatch_root_queue_push_override中傳遞的n為1。 - 將
n賦值給remaining代表需要創(chuàng)建多少個線程,傳遞1是因為這里每次調(diào)用創(chuàng)建一個,多次調(diào)用多次創(chuàng)建。有的情況會傳n不為1在內(nèi)部就會走循環(huán)邏輯了(比如_dispatch_worker_thread中)。 - 全局并發(fā)/自定義并發(fā)直接走
_pthread_workqueue_addthreads的邏輯增加線程。 - 后面的邏輯(
NSThread start會走這里的邏輯)。-
can_request表示當前能夠創(chuàng)建的線程數(shù)量,remaining表示申請創(chuàng)建的線程數(shù)量。 - 當申請的大于能創(chuàng)建的線程數(shù)量的時候進行申請數(shù)量修正為能創(chuàng)建數(shù)量
remaining = can_request。 - 這里循環(huán)修復中用到了
dgq_thread_pool_size(線程池大?。?。這個參數(shù)將在后面介紹。 - 調(diào)用
pthread_create創(chuàng)建線程。(循環(huán)調(diào)用) - 進行一些
log記錄??梢钥吹?code>dwStackSize給的是64M。
-
3.1.2 dgq_thread_pool_size
struct dispatch_queue_global_s _dispatch_mgr_root_queue = {
DISPATCH_GLOBAL_OBJECT_HEADER(queue_global),
.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE,
.do_ctxt = &_dispatch_mgr_root_queue_pthread_context,
.dq_label = "com.apple.root.libdispatch-manager",
.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL),
.dq_priority = DISPATCH_PRIORITY_FLAG_MANAGER |
DISPATCH_PRIORITY_SATURATED_OVERRIDE,
.dq_serialnum = 3,
.dgq_thread_pool_size = 1,
};
在全局并發(fā)隊列中初始化為1,自定義并發(fā)沒有賦值為0。
在進行DQF_WIDTH(并行/串行區(qū)分)設置的時候有如下宏定義:
//全局并發(fā)比自定義并發(fā) DQF_WIDTH 大 1
#define DISPATCH_QUEUE_WIDTH_FULL 0x1000ull
//全局并發(fā)
#define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
//自定義并發(fā)
#define DISPATCH_QUEUE_WIDTH_MAX (DISPATCH_QUEUE_WIDTH_FULL - 2)
搜索dgq_thread_pool_size會發(fā)現(xiàn):
#ifndef DISPATCH_WORKQ_MAX_PTHREAD_COUNT
#define DISPATCH_WORKQ_MAX_PTHREAD_COUNT 255
#endif
#if DISPATCH_USE_PTHREAD_POOL
static inline void
_dispatch_root_queue_init_pthread_pool(dispatch_queue_global_t dq,
int pool_size, dispatch_priority_t pri)
{
dispatch_pthread_root_queue_context_t pqc = dq->do_ctxt;
int thread_pool_size = DISPATCH_WORKQ_MAX_PTHREAD_COUNT;
if (!(pri & DISPATCH_PRIORITY_FLAG_OVERCOMMIT)) {
thread_pool_size = (int32_t)dispatch_hw_config(active_cpus);
}
if (pool_size && pool_size < thread_pool_size) thread_pool_size = pool_size;
dq->dgq_thread_pool_size = thread_pool_size;
……
}
- 在
_dispatch_root_queue_init_pthread_pool對thread_pool_size進行賦值為thread_pool_size也就是255。
在_dispatch_worker_thread中有自增操作:
static void *
_dispatch_worker_thread(void *context)
{
……
//++
(void)os_atomic_inc2o(dq, dgq_thread_pool_size, release);
_dispatch_root_queue_poke(dq, 1, 0);
_dispatch_release(dq); // retained in _dispatch_root_queue_poke_slow
return NULL;
}
-
os_atomic_inc2o對dgq_thread_pool_size進行自增,一直到最大值為止。
3.1.3 線程數(shù)量的計算
線程數(shù)量計算就涉及內(nèi)核態(tài)和用戶態(tài)內(nèi)存打消了,比如iOS 4G內(nèi)存,有1G會被分配給內(nèi)核。
在官網(wǎng)的介紹中有如下描述:

- 內(nèi)核數(shù)據(jù)結(jié)構(gòu)占用大?。?code>1 KB。
- ??臻g大小:非主線程
16KB ~ 512 KB,主線程macOS 8 MB、iOS 1 MB。 - 創(chuàng)建花費時間:大約
90 us(微秒)。
更多關(guān)于多線程的介紹參考Threading Programming Guide
線程至少分配16KB空間,如果全部按照16KB來分配數(shù)量為:1024 * 1024 / 16 = 64 * 1024 個。
按照512KB分配就有1024 * 1024 / 512 = 2048個。
那么可以分配的數(shù)量范圍為2048 ~ 64 * 1024。
但是內(nèi)核肯定不能全部用來創(chuàng)建線程,并且過多線程需要來回切換也會影響性能,在上面的代碼中有個64M數(shù)據(jù)的定義:
dwStackSize = 64 * 1024;//64M
那么是否是限制64M呢?如果按照64M計算線程數(shù)量應該在128 ~ 4096。
3.1.4 代碼驗證
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"test");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"test");
});

- 經(jīng)過驗證全局并發(fā)隊列/自定義并發(fā)隊列都走這里。
對pthread_create打斷點發(fā)現(xiàn)在[NSThread start]的時候會走:

??:GCD中當前已經(jīng)被調(diào)度的任務沒有辦法終止。
3.2 串行隊列
串行隊列的線程創(chuàng)建跟蹤并沒有走_pthread_workqueue_addthreads和pthread_create。那么說明是獲取的線程,不是創(chuàng)建。也就是從3~16中獲取的線程。
這個時候當前面的線程都被占用的時候,則應該會創(chuàng)建:
for (NSInteger i = 0; i < 30; i++) {
NSString *str = [NSString stringWithFormat:@"test_%@",@(i)];
dispatch_queue_t queue = dispatch_queue_create(str.UTF8String, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
}
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"special thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
});
這個時候斷點就走_pthread_workqueue_addthreads邏輯了:
* thread #13, queue = 'com.apple.root.user-initiated-qos', stop reason = breakpoint 3.1
* frame #0: 0x00007fff611638de libsystem_pthread.dylib`_pthread_workqueue_addthreads
frame #1: 0x000000010398d511 libdispatch.dylib`_dispatch_root_queue_poke_slow + 185
frame #2: 0x00000001039924cb libdispatch.dylib`_dispatch_root_queue_drain + 290
frame #3: 0x0000000103992e6d libdispatch.dylib`_dispatch_worker_thread2 + 135
frame #4: 0x00007fff611639f7 libsystem_pthread.dylib`_pthread_wqthread + 220
frame #5: 0x00007fff61162b77 libsystem_pthread.dylib`start_wqthread + 15
四、驗證線程大小以及數(shù)量
對于自定義隊列來說分為兩種情況:同一隊列以及不同隊列。
4.1 自定義并發(fā)
4.1.1 創(chuàng)建多個隊列
for (NSInteger i = 0; i < 10000; i++) {//8 7 2 1 沒有 68 - 4 = 64個。
NSString *str = [NSString stringWithFormat:@"test_%@",@(i)];
dispatch_queue_t queue = dispatch_queue_create(str.UTF8String, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
NSLog(@"count:%@",@(i));
}
輸出:

-
number取值從3~68,沒有1、2、7、8總共64個線程,每個512KB。 - 當
100秒后接著執(zhí)行后面的任務。
4.1.2 創(chuàng)建單個隊列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10000; i++) {//8 7 2 1 沒有 68 - 4 = 64個。
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
NSLog(@"count:%@",@(i));
}
-
number取值從3~67,沒有1、2、6總共64個線程,每個512KB。 - 當
100秒后接著執(zhí)行后面的任務。
4.2 自定義串行
4.2.1 創(chuàng)建多個隊列
for (NSInteger i = 0; i < 10000; i++) {
NSString *str = [NSString stringWithFormat:@"test_%@",@(i)];
dispatch_queue_t queue = dispatch_queue_create(str.UTF8String, NULL);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
NSLog(@"count:%@",@(i));
}
輸出:
thread:<NSThread: 0x28002fa00>{number = 506, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x28009c4c0>{number = 504, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x28002f500>{number = 508, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x280028840>{number = 507, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x280029480>{number = 509, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x28009cc80>{number = 510, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x28002aac0>{number = 514, name = (null)} thread size:512 KB isMain:0
-
number取值從2~514,沒有1,2??偣?code>512個線程,每個512 KB。 - 當
100秒后接著執(zhí)行后面的任務。
4.2.2 創(chuàng)建單個隊列
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
});
NSLog(@"count:%@",@(i));
}
輸出:
thread:<NSThread: 0x28382e940>{number = 7, name = (null)} thread size:512 KB isMain:0
- 只創(chuàng)建
1個線程,任務逐個執(zhí)行。 - 多次測試線程仍然只在
3~16之間。
那么當3~16線程都被占用的時候,應該會會創(chuàng)建新的線程。
以下代碼模擬前30個線程被占用:
for (NSInteger i = 0; i < 30; i++) {
NSString *str = [NSString stringWithFormat:@"test_%@",@(i)];
dispatch_queue_t queue = dispatch_queue_create(str.UTF8String, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
}
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
dispatch_async(queue, ^{
sleep(5);
NSLog(@"special thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
});
輸出:
thread:<NSThread: 0x6000031d0280>{number = 16, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d0480>{number = 3, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031ccb40>{number = 20, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031a5600>{number = 18, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d8640>{number = 21, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031a5580>{number = 27, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031cccc0>{number = 6, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031e0d40>{number = 13, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003185f40>{number = 5, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003189880>{number = 28, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031c6d00>{number = 24, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d86c0>{number = 29, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031dc140>{number = 26, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d0080>{number = 34, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d8180>{number = 12, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031dc180>{number = 30, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003186880>{number = 14, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031ca740>{number = 10, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031dc100>{number = 22, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031dc1c0>{number = 35, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d8e80>{number = 17, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d0300>{number = 25, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x60000318c500>{number = 31, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031dc0c0>{number = 15, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031a6440>{number = 4, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d8380>{number = 19, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d8580>{number = 33, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031c9040>{number = 23, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d0000>{number = 11, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000031d02c0>{number = 32, name = (null)} thread size:512 KB isMain:0
special thread:<NSThread: 0x600003186e40>{number = 36, name = (null)} thread size:512 KB isMain:0
- 所有線程
取值從3~36,沒有1、2、7、8、 9。 - 串行隊列現(xiàn)在就創(chuàng)建了
36線程。
4.3 全局并行
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
sleep(100);
});
NSLog(@"count:%@",@(i));
}
輸出:
thread:<NSThread: 0x60000397d480>{number = 48, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003699040>{number = 56, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003969100>{number = 63, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036ace40>{number = 60, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036aeb00>{number = 68, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003977b00>{number = 55, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036aea40>{number = 67, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003695840>{number = 41, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x60000396d840>{number = 61, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036a5a00>{number = 47, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x600003973c40>{number = 20, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x60000397eac0>{number = 24, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036a5680>{number = 57, name = (null)} thread size:512 KB isMain:0
thread:<NSThread: 0x6000036a0680>{number = 4, name = (null)} thread size:512 KB isMain:0
-
number取值從3~68,沒有1、2、8、9。總共64個線程,每個512KB。 - 當
100秒后接著執(zhí)行后面的任務。
4.4 主線程
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
sleep(3);
NSLog(@"thread:%@ thread size:%zd KB isMain:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
});
NSLog(@"count:%@",@(i));
}
輸出:
thread:<NSThread: 0x600000b60880>{number = 1, name = main} thread size:512 KB isMain:1
-
main也就只有1主線程了。依次執(zhí)行(間隔3秒)。
??模擬器與真機結(jié)論相同
4.5 特殊線程解析
根據(jù)上面的分析線程1、2、6、7、8、9應該有特殊處理。
-
1主線程沒有什么好說的。 -
2是管理隊列。 -
6、7、8、9對應:
_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,
),
-
BACKGROUND后臺,UTILITY公共的。 -
DISPATCH_PRIORITY_FLAG_OVERCOMMIT表示過量的情況。
#define DISPATCH_PRIORITY_FLAG_OVERCOMMIT ((dispatch_priority_t)0x80000000) // _PTHREAD_PRIORITY_OVERCOMMIT_FLAG
4.6 小結(jié)
-
自定義并發(fā)隊列:最大創(chuàng)建
64個線程。此時總空間64M。- 創(chuàng)建多個隊列取值
3~68,沒有1、2、7、8。 - 創(chuàng)建單個隊列取值
3~67,沒有1、2、6。
- 創(chuàng)建多個隊列取值
-
自定義串行隊列:
- 創(chuàng)建多個隊列,最大創(chuàng)建
512個線程(2~514,沒有1、2)。此時總空間256M。 - 創(chuàng)建單個隊列,只創(chuàng)建
1個線程(先從3~16中獲取,被占用則創(chuàng)建)。
- 創(chuàng)建多個隊列,最大創(chuàng)建
- 全局并行隊列:最大創(chuàng)建
64個線程(3~68,沒有1、2、8、9)。 - 主線程:不創(chuàng)建線程,只有主線程
1。 - 線程大小都是
512KB。 - 多余的任務在線程執(zhí)行完任務后才能再次執(zhí)行。對應案例中
sleep結(jié)束。
與上篇文章結(jié)合有如下調(diào)用流程:
同步異步單例函數(shù)調(diào)用流程以及線程創(chuàng)建和回調(diào)流程

