上篇文章 GCD底层分析(一) 分析了函数、队列的创建、以及同步和异步函数的调用流程。
那么有以下疑问:
- 死锁是怎么造成的?
- 同步和异步函数能否开辟线程?
这篇文章将着重分析这几个问题。
一、死锁的本质
1.1 调用逻辑分析
在源码中同步函数串行队列会调用到_dispatch_sync_f_inline
的逻辑:
_dispatch_barrier_sync_f
会调用到_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
函数中报错,而是在__DISPATCH_WAIT_FOR_QUEUE__
:
_dispatch_sync_f_slow
调用__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__
中报错判断函数是_dq_state_drain_locked_by
:
- 传递的参数是
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 ^ tid
为0
,& DLOCK_OWNER_MASK
值才为0
。 - 意味着只有
lock_value
与tid
相同,值才为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_sync
与dispatch_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
中传递的floor
为0
。 - 在
_dispatch_root_queue_push_override
中传递的n
为1
。 - 将
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_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 线程数量的计算
线程数量计算就涉及内核态和用户态内存打消了,比如iOS 4G
内存,有1G
会被分配给内核。
在官网的介绍中有如下描述:
- 内核数据结构占用大小:
1 KB
。 - 栈空间大小:非主线程
16KB ~ 512 KB
,主线程macOS 8 MB
、iOS 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");
});
- 经过验证全局并发队列/自定义并发队列都走这里。
对pthread_create
打断点发现在[NSThread start]
的时候会走:
⚠️:GCD
中当前已经被调度的任务没有办法终止。
3.2 串行队列
串行队列的线程创建跟踪并没有走_pthread_workqueue_addthreads
和pthread_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));
}
输出:
-
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
结束。
与上篇文章结合有如下调用流程:
同步异步单例函数调用流程以及线程创建和回调流程