GCD底層分析(二):死鎖、單例以及線程的創(chuàng)建和數(shù)量

上篇文章 GCD底層分析(一) 分析了函數(shù)、隊列的創(chuàng)建、以及同步和異步函數(shù)的調(diào)用流程。
那么有以下疑問:

  1. 死鎖是怎么造成的?
  2. 同步和異步函數(shù)能否開辟線程?

這篇文章將著重分析這幾個問題。

一、死鎖的本質(zhì)

1.1 調(diào)用邏輯分析

在源碼中同步函數(shù)串行隊列會調(diào)用到_dispatch_sync_f_inline的邏輯:

image.png

_dispatch_barrier_sync_f會調(diào)用到_dispatch_barrier_sync_f_inline

image.png

  • 死鎖會走_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");
}
image.png
  • 無論是否主線程死鎖都走同樣的邏輯。
  • 并不是在_dispatch_sync_f_slow函數(shù)中報錯,而是在__DISPATCH_WAIT_FOR_QUEUE__
    image.png

_dispatch_sync_f_slow調(diào)用__DISPATCH_WAIT_FOR_QUEUE__報錯:

image.png

  • 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

image.png

  • 傳遞的參數(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 ^ tid0,& DLOCK_OWNER_MASK值才為0。
  • 意味著只有lock_valuetid相同,值才為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_syncdispatch_async的調(diào)用邏輯(文章的第六部分 GCD 函數(shù)調(diào)用流程 )。當時忽略了線程創(chuàng)建的問題。

根據(jù)分析的流程同步函數(shù)是直接執(zhí)行block回調(diào)的,不會開辟線程。那么異步函數(shù)是在什么時候開辟線程的呢?

異步函數(shù)調(diào)用流程
  • 主隊列在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中傳遞的floor0。
  • _dispatch_root_queue_push_override中傳遞的n1。
  • 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_poolthread_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_inc2odgq_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)的介紹中有如下描述:

image.png

  • 內(nèi)核數(shù)據(jù)結(jié)構(gòu)占用大?。?code>1 KB。
  • ??臻g大小:非主線程16KB ~ 512 KB,主線程macOS 8 MBiOS 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");
});

image.png
  • 經(jīng)過驗證全局并發(fā)隊列/自定義并發(fā)隊列都走這里。

pthread_create打斷點發(fā)現(xiàn)在[NSThread start]的時候會走:

image.png

??:GCD中當前已經(jīng)被調(diào)度的任務沒有辦法終止。

3.2 串行隊列

串行隊列的線程創(chuàng)建跟蹤并沒有走_pthread_workqueue_addthreadspthread_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));
}

輸出:


image.png
  • 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個線程,每個512 KB。
  • 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)建512個線程(2~514,沒有1、2)。此時總空間256M。
    • 創(chuàng)建單個隊列,只創(chuàng)建1個線程(先從3~16中獲取,被占用則創(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)流程

同步異步單例函數(shù)調(diào)用流程以及線程創(chuàng)建和回調(diào)邏輯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容