GCD底层分析(二):死锁、单例以及线程的创建和数量

上篇文章 GCD底层分析(一) 分析了函数、队列的创建、以及同步和异步函数的调用流程。
那么有以下疑问:

  1. 死锁是怎么造成的?
  2. 同步和异步函数能否开辟线程?

这篇文章将着重分析这几个问题。

一、死锁的本质

1.1 调用逻辑分析

在源码中同步函数串行队列会调用到_dispatch_sync_f_inline的逻辑:

image.png

_dispatch_barrier_sync_f会调用到_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函数中报错,而是在__DISPATCH_WAIT_FOR_QUEUE__
    image.png

_dispatch_sync_f_slow调用__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__中报错判断函数是_dq_state_drain_locked_by

image.png

  • 传递的参数是dq_state(同步函数传递参数的队列状态)以及dsc_waiter(同步函数所在队列线程的id)。

_dq_state_drain_locked_by的实现:

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);
}

直接调用_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。说明要等待的线程与要调起的线程是同一个,构成了矛盾。

1.4小结

死锁现象

  • 线程因为同步函数的原因等着执行任务。
  • 串行队列等着线程的任务执行完毕再执行自己的任务。
  • 串行队列和线程相互等待造成死锁。

死锁的本质
要等待的线程与要调起的线程是同一个,构成了矛盾。(队列调用dispatch_sync,但是队列已经被当前线程持有了。)

死锁的条件

  • 串行队列(queue)。
  • queue中调用dispatch_sync同步函数添加任务到queue

也就是test中的测试代码,主线程和自定义串行队列的例子。

二、单例的底层原理(dispatch_once

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"test");
});

2.1 dispatch_once

dispatch_once源码实现如下:

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
    dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
  • val是全局静态变量onceToken(dispatch_once_t)。

dispatch_once_f源码如下:

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
    //val标记 强转()
    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);
    //已经执行过直接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)) {
        //执行任务
        return _dispatch_once_callout(l, ctxt, func);
    }
    //没有标记执行过,并且被锁锁住了走这里的逻辑。等待开锁。
    return _dispatch_once_wait(l);
}
  • 根据val判断是否已经执行过,执行过直接返回。
  • 执行过但是没有标记,直接进行标记返回_dispatch_once_mark_done_if_quiesced
  • 没有被锁住执行任务_dispatch_once_callout
  • 没有标记执行过,并且被锁锁住了_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 调用的是 __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执行任务:

static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
        dispatch_function_t func)
{
    //block回调
    _dispatch_client_callout(ctxt, func);
    _dispatch_once_gate_broadcast(l);
}
  • 调用block回调。
  • _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
    //设置状态
    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);
}

调用_dispatch_once_mark_done设置状态:

static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    //先匹配,再设置状态为Done。
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
  • 这里与_dispatch_once_mark_done_if_quiesced有点像。

单例底层调用的是dispatch_once_f,在_dispatch_root_queues_init中调用的就是这个函数。

三、GCD是如何创建线程的

上篇文章分析了dispatch_syncdispatch_async的调用逻辑(文章的第六部分 GCD 函数调用流程 )。当时忽略了线程创建的问题。

根据分析的流程同步函数是直接执行block回调的,不会开辟线程。那么异步函数是在什么时候开辟线程的呢?

异步函数调用流程
  • 主队列在App启动main之后就创建好了。

3.1 全局队列 & 并行队列

3.1.1 源码分析

根据上面的流程直接定位到_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;//进来一次异步全局并发队列创建一个线程。相当于循环一次创建一个。
#if !defined(_WIN32)
    int r = ENOSYS;
#endif
    //单例,调用一次。设置 _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
    //全局并发,自定义并发都走这里
    if (dx_type(dq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE)
#endif
    {
        _dispatch_root_queue_debug("requesting new worker thread for global "
                "queue: %p", dq);
        //创建线程
        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 
    //加载 t_count = 能够创建的线程数量
    t_count = os_atomic_load2o(dq, dgq_thread_pool_size, ordered);
    /*
     //全局并发比自定义并发 DFQ_WIDTH 大 1
     #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)
    */
    //普通的do-while循环 判断 dgq_thread_pool_size 线程池大小(全局并发从1开始,自定义并发从0开始)。由于大1,所以这里进行
    do {
        //floor为传递过来的,0  can_request = 能够创建的数量
        can_request = t_count < floor ? 0 : t_count - floor;
        // remaining = 需要创建的数量(为n传进来的参数)
        // 需要创建的数量大于能创建的?
        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 修复为能提供的数量
            remaining = can_request;
        }
        //修复后为0则表示已经达到最大值了,不能再创建了。直接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代表需要创建多少个线程,传递1是因为这里每次调用创建一个,多次调用多次创建。有的情况会传n不为1在内部就会走循环逻辑了(比如_dispatch_worker_thread中)。
  • 全局并发/自定义并发直接走_pthread_workqueue_addthreads的逻辑增加线程。
  • 后面的逻辑(NSThread start会走这里的逻辑)。
    • can_request表示当前能够创建的线程数量,remaining表示申请创建的线程数量。
    • 当申请的大于能创建的线程数量的时候进行申请数量修正为能创建数量remaining = can_request
    • 这里循环修复中用到了dgq_thread_pool_size(线程池大小)。这个参数将在后面介绍。
    • 调用pthread_create创建线程。(循环调用)
    • 进行一些log记录。可以看到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,
};

在全局并发队列中初始化为1,自定义并发没有赋值为0
在进行DQF_WIDTH(并行/串行区分)设置的时候有如下宏定义:

 //全局并发比自定义并发 DQF_WIDTH 大 1
 #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)

搜索dgq_thread_pool_size会发现:

#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 线程数量的计算

线程数量计算就涉及内核态和用户态内存打消了,比如iOS 4G内存,有1G会被分配给内核。
在官网的介绍中有如下描述:

image.png

  • 内核数据结构占用大小:1 KB
  • 栈空间大小:非主线程16KB ~ 512 KB,主线程macOS 8 MBiOS 1 MB
  • 创建花费时间:大约90 us(微秒)。

更多关于多线程的介绍参考Threading Programming Guide

线程至少分配16KB空间,如果全部按照16KB来分配数量为:1024 * 1024 / 16 = 64 * 1024 个。
按照512KB分配就有1024 * 1024 / 512 = 2048个。
那么可以分配的数量范围为2048 ~ 64 * 1024
但是内核肯定不能全部用来创建线程,并且过多线程需要来回切换也会影响性能,在上面的代码中有个64M数据的定义:

dwStackSize = 64 * 1024;//64M

那么是否是限制64M呢?如果按照64M计算线程数量应该在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
  • 经过验证全局并发队列/自定义并发队列都走这里。

pthread_create打断点发现在[NSThread start]的时候会走:

image.png

⚠️:GCD中当前已经被调度的任务没有办法终止。

3.2 串行队列

串行队列的线程创建跟踪并没有走_pthread_workqueue_addthreadspthread_create。那么说明是获取的线程,不是创建。也就是从3~16中获取的线程。
这个时候当前面的线程都被占用的时候,则应该会创建:

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

四、验证线程大小以及数量

对于自定义队列来说分为两种情况:同一队列以及不同队列。

4.1 自定义并发

4.1.1 创建多个队列

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秒后接着执行后面的任务。

4.1.2 创建单个队列

    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秒后接着执行后面的任务。

4.2 自定义串行

4.2.1 创建多个队列

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:{number = 506, name = (null)} thread size:512 KB  isMain:0
thread:{number = 504, name = (null)} thread size:512 KB  isMain:0
thread:{number = 508, name = (null)} thread size:512 KB  isMain:0
thread:{number = 507, name = (null)} thread size:512 KB  isMain:0
thread:{number = 509, name = (null)} thread size:512 KB  isMain:0
thread:{number = 510, name = (null)} thread size:512 KB  isMain:0
thread:{number = 514, name = (null)} thread size:512 KB  isMain:0
  • number取值从2~514,没有1,2。总共512个线程,每个512 KB
  • 100秒后接着执行后面的任务。

4.2.2 创建单个队列

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:{number = 7, name = (null)} thread size:512 KB  isMain:0
  • 只创建1个线程,任务逐个执行。
  • 多次测试线程仍然只在3~16之间。

那么当3~16线程都被占用的时候,应该会会创建新的线程。
以下代码模拟前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:{number = 16, name = (null)} thread size:512 KB  isMain:0
thread:{number = 3, name = (null)} thread size:512 KB  isMain:0
thread:{number = 20, name = (null)} thread size:512 KB  isMain:0
thread:{number = 18, name = (null)} thread size:512 KB  isMain:0
thread:{number = 21, name = (null)} thread size:512 KB  isMain:0
thread:{number = 27, name = (null)} thread size:512 KB  isMain:0
thread:{number = 6, name = (null)} thread size:512 KB  isMain:0
thread:{number = 13, name = (null)} thread size:512 KB  isMain:0
thread:{number = 5, name = (null)} thread size:512 KB  isMain:0
thread:{number = 28, name = (null)} thread size:512 KB  isMain:0
thread:{number = 24, name = (null)} thread size:512 KB  isMain:0
thread:{number = 29, name = (null)} thread size:512 KB  isMain:0
thread:{number = 26, name = (null)} thread size:512 KB  isMain:0
thread:{number = 34, name = (null)} thread size:512 KB  isMain:0
thread:{number = 12, name = (null)} thread size:512 KB  isMain:0
thread:{number = 30, name = (null)} thread size:512 KB  isMain:0
thread:{number = 14, name = (null)} thread size:512 KB  isMain:0
thread:{number = 10, name = (null)} thread size:512 KB  isMain:0
thread:{number = 22, name = (null)} thread size:512 KB  isMain:0
thread:{number = 35, name = (null)} thread size:512 KB  isMain:0
thread:{number = 17, name = (null)} thread size:512 KB  isMain:0
thread:{number = 25, name = (null)} thread size:512 KB  isMain:0
thread:{number = 31, name = (null)} thread size:512 KB  isMain:0
thread:{number = 15, name = (null)} thread size:512 KB  isMain:0
thread:{number = 4, name = (null)} thread size:512 KB  isMain:0
thread:{number = 19, name = (null)} thread size:512 KB  isMain:0
thread:{number = 33, name = (null)} thread size:512 KB  isMain:0
thread:{number = 23, name = (null)} thread size:512 KB  isMain:0
thread:{number = 11, name = (null)} thread size:512 KB  isMain:0
thread:{number = 32, name = (null)} thread size:512 KB  isMain:0
special thread:{number = 36, name = (null)} thread size:512 KB  isMain:0
  • 所有线程取值3~36,没有1、2、7、8、 9
  • 串行队列现在就创建了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:{number = 48, name = (null)} thread size:512 KB  isMain:0
thread:{number = 56, name = (null)} thread size:512 KB  isMain:0
thread:{number = 63, name = (null)} thread size:512 KB  isMain:0
thread:{number = 60, name = (null)} thread size:512 KB  isMain:0
thread:{number = 68, name = (null)} thread size:512 KB  isMain:0
thread:{number = 55, name = (null)} thread size:512 KB  isMain:0
thread:{number = 67, name = (null)} thread size:512 KB  isMain:0
thread:{number = 41, name = (null)} thread size:512 KB  isMain:0
thread:{number = 61, name = (null)} thread size:512 KB  isMain:0
thread:{number = 47, name = (null)} thread size:512 KB  isMain:0
thread:{number = 20, name = (null)} thread size:512 KB  isMain:0
thread:{number = 24, name = (null)} thread size:512 KB  isMain:0
thread:{number = 57, name = (null)} thread size:512 KB  isMain:0
thread:{number = 4, name = (null)} thread size:512 KB  isMain:0
  • number取值从3~68,没有1、2、8、9。总共64个线程,每个512 KB。
  • 100秒后接着执行后面的任务。

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:{number = 1, name = main} thread size:512 KB  isMain:1
  • main也就只有1主线程了。依次执行(间隔3秒)。

⚠️模拟器与真机结论相同

4.5 特殊线程解析

根据上面的分析线程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 小结

  • 自定义并发队列:最大创建64个线程。此时总空间64M
    • 创建多个队列取值3~68,没有1、2、7、8
    • 创建单个队列取值3~67,没有1、2、6
  • 自定义串行队列:
    • 创建多个队列,最大创建512个线程(2~514,没有1、2)。此时总空间256M
    • 创建单个队列,只创建1个线程(先从3~16中获取,被占用则创建)。
  • 全局并行队列:最大创建64个线程(3~68,没有1、2、8、9)。
  • 主线程:不创建线程,只有主线程1
  • 线程大小都是512KB
  • 多余的任务在线程执行完任务后才能再次执行。对应案例中sleep结束。

与上篇文章结合有如下调用流程:
同步异步单例函数调用流程以及线程创建和回调流程

同步异步单例函数调用流程以及线程创建和回调逻辑

你可能感兴趣的:(GCD底层分析(二):死锁、单例以及线程的创建和数量)