底层原理-22-GCD分析(上)

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

1. GCD简介

在Mac OS x v10.6中引入的Grand Central Dispatch是线程的另一种替代品,它允许您专注于需要执行的任务,而不是线程管理。使用GCD,您可以定义要执行的任务并将其添加到工作队列中,该队列处理在适当线程上的任务调度。工作队列考虑了可用核心的数量和当前负载,以比使用线程更有效地执行任务。
GCD是纯 C 语言,提供了非常多强大的函数。它的优势:

  • GCD 是苹果公司为多核的并行运算提出的解决方案

  • GCD 会自动利用更多的CPU内核(比如双核、四核)

  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程) 程序员只需要告诉 

  • GCD 想要执行什么任务,不需要编写任何线程管理代码

1.1 函数

任务使用 block 封装
* 任务的 block 没有参数也没有返回值

* 执行任务的函数\

  •  异步 dispatch_async

不用等待当前语句执行完毕,就可以执行下一条语句

* 会开启线程执行 block 的任务

* 异步是多线程的代名词

  •  同步 dispatch_sync

* 必须等待当前语句执行完毕,才会执行下一条语句

不会开启线程

* 在当前执行 block 的任务

1.2 串行队列和并行队列

队列:执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。每读取一个任务,则动队列中释放一个任务。队列分为串行队列和并行队列

串行队列:每次只执行一个任务,要等上一个任务执行完成才能执行下一个任务,可以想象成一个串行电路,朝着一个方向,也可以称为护犊子,上一个任务没有走完,无法走到下一个任务。

//串行队列
dispatch_queue_t queue = dispatch_queue_create("keke", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue1 = dispatch_queue_create("serial_k", NULL);//NULL表示默认串行队列
复制代码

并行队列:每次可以执行多个任务,不需要等待上个任务完成就可以执行下一个任务,可以想象并行电路,朝着一个方向遇到分流就会都去走。同时可以执行多个任务

//并行队列创建
dispatch_queue_t queue = dispatch_queue_create("kebei", DISPATCH_QUEUE_CONCURRENT);
复制代码

未命名文件-8.jpg

1.3 函数和队列

函数有同步函数和异步函数,队列有同步队列和异步队列,因此他们有4种情况。

1.3.1 同步函数串行队列

-(void)kb_testSyncFunctionSerialQueue

{

    NSLog(@"当前 线程 - %@", [NSThread currentThread]);

    

    dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(serialQueue, ^{

        sleep(2);

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务1");

    });

    

    dispatch_sync(serialQueue, ^{

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务2");

    });

    

}
复制代码

image.png

  • 不会开启线程,在当前线程执行任务。
  • 任务串行执行,一个接着一个。
  • 会产生堵塞。

1.3.2 同步函数并发队列

-(void)kb_testSyncFunctionInConcurrentQueue

{

    NSLog(@"当前 线程 - %@", [NSThread currentThread]);

    

    dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_sync(concurrentQueue, ^{

        sleep(2);

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务1");

    });

    

    dispatch_sync(concurrentQueue, ^{

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务2");

    });



}
复制代码

image.png

  • 不会开启线程,在当前线程执行任务。
  • 任务串行执行,一个接着一个。
  • 不产生堵塞

1.3.3 异步函数串行队列

-(void)kb_testAsyncFunctionInSerialQueue

{

    

    NSLog(@"当前 线程 - %@", [NSThread currentThread]);



    dispatch_queue_t serialQueue = dispatch_queue_create("kb_serial", DISPATCH_QUEUE_SERIAL);

    

    dispatch_async(serialQueue, ^{

        sleep(2);

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务1");

    });

    

    dispatch_async(serialQueue, ^{

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务2");

    });

  

}
复制代码

image.png

  • 开启新的线程,新线程执行任务。
  • 任务一个接着一个接着一个串行。
  • 不产生堵塞。

1.3.4 异步函数并发队列

-(void)kb_testAsyncFunctionInConcurrentQueue

{

    NSLog(@"当前 线程 - %@", [NSThread currentThread]);

    dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrent", DISPATCH_QUEUE_CONCURRENT);

    

    dispatch_async(concurrentQueue, ^{

        sleep(2);

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务1");

    });

    

    

    dispatch_async(concurrentQueue, ^{

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"任务2");

    });


}
复制代码

image.png

  • 开启线程,在当前线程执行任务。
  • 任务异步执行,没有顺序,和CPU调度有关。
  • 不产生堵塞。

1.4 主队列和全局队列

  • 主队列

专⻔用来在主线程上调度任务的串行队列
不会开启线程 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

dispatch_queue_t mainQueue = dispatch_get_main_queue();
复制代码
  • 全局并发队列

为了方便程序员的使用,苹果提供了全局队列 
全局队列是一个并发队列 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

dispatch_queue_t globQueue = dispatch_get_global_queue(0, 0);
复制代码

1.4.1 主队列和同步函数

image.png
主队列 要执行NSLog 同步block任务。同步block 要等block中的打印任务完成才可以同步,但是block打印任务又要等block同步了才执行打印。因此相互等待,造成死锁
死锁现象

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

1.4.2 主队列和异步函数

image.png
不开辟新线程,当前线程按顺序执行

1.4.3 全局队列和同步函数

image.png
同步函数不开辟新的线程,任务按顺序执行

1.4.4 全局队列和异步函数

image.png
任务并发执行,不按顺序,会开辟新的线程。

2. GCD常见面试题

2.1 下面有几种类型的队列

//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("kb_serialQueue", NULL);
    
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("kb_concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);


复制代码

2种,串行队列并行队列
主队列本质是串行队列,定义了main的标识,线程编号是1.
全局队列的本质是并行队列

2.2 微博线程执行顺序面试题

下面的代码执行顺序

- (void)wbinterDemo{//

    dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);


    dispatch_async(queue, ^{ // 耗时

        NSLog(@"1");

    });

    

    dispatch_async(queue, ^{

        NSLog(@"2");

    });

    

    // 堵塞哪一行

    dispatch_sync(queue, ^{ NSLog(@"3"); });

    

    NSLog(@"0");


    dispatch_async(queue, ^{

        NSLog(@"7");

    });

    dispatch_async(queue, ^{

        NSLog(@"8");

    });

    dispatch_async(queue, ^{

        NSLog(@"9");

    });

    // A: 1230789

    // B: 1237890

    // C: 3120798

    // D: 2137890

}

复制代码

答案是A|C

image.png
c
image.png
分析:在并发队列中,异步函数是没有执行顺序的,1和2是可能先可能后,3同步函数会执行玩才能执行下面的,因此123是先执行,不分先后;0是在同步之后因此3完成之后才是0,7,8,9时异步执行但是都会在0之后,顺序原因,7,8,9 本身并发没有先后顺序。因此123乱序-0 -789乱序,0一定是在第4位,所以选AC

  • 将上面的队列换成串行队列选什么
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_SERIAL);
复制代码

选A,按顺序执行,在串行队列中按照先进先出的原则,异步函数会开辟一个新的线程执行任务,同步函数不会,但是在串行队列还是按FIFO原则。

2.3 异步函数嵌套异步函数的调用顺序

- (void)textDemo{

    

    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"1");

    // 耗时

    dispatch_async(queue, ^{

        NSLog(@"2");

        dispatch_async(queue, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });


    NSLog(@"5");


}
复制代码

调用顺序为:1,5,2,4,3. 分析如下:

  1. 主线程中按顺序执行1->并发队列异步函数->5,由于5比较简单先完成了5;
  2. 在异步函数中2->异步函数3->4, 由于4比较简单先完成了4,之后是3;
  3. 因此执行顺序打印结果是1,5,2,4,3

添加sleep进行模拟:
image.png

  • 延伸:如果将上面的队列改为串行队列DISPATCH_QUEUE_SERIAL,打印结果是什么?

打印结果是1,2,5,4,31,5,2,4,3。分析如下:

  1. 主线程按顺序执行 1->串行队列异步函数->5,由于5比较简单先完成了5,但是有可能堵塞会进入异步函称的block,会先完成2,我在打印的过程出现了一次2在5之前。
  2. 进入异步函数的block中,按顺序执行任务2->4->串行队列异步函数3,由于4在串行队列所谓的主线程中,子线程要等主线程完成完成才会执行子线程.
  3. 可以添加slepp(1)来堵塞线程模拟

一般异步函数会开辟新的线程,队列并发执行,子线程异步函数会开辟新的线程,由于是并发所以根据耗时多少打印顺序按耗时时间来。

image.png
串行队列中,任务是按一个一个顺序执行的,因此他们共用一个线程,任务4在开辟的线程中执行,完成后才会把线程给任务3来执行任务,之后传给任务13.这个过程不以时间长短来,而已顺序来执行

2.4 异步函数嵌套同步函数的调用顺序

- (void)textDemo2{

    // 同步队列

    dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);

    NSLog(@"1");

    // 异步函数

    dispatch_async(queue, ^{

        NSLog(@"当前 线程 - %@", [NSThread currentThread]);

        NSLog(@"2");

        // 同步

        dispatch_sync(queue, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");



}
复制代码

执行顺序 1,5,2,3,4。分析:

  1. 外层执行1->异步函数->5,在异步函数内部执行并发任务。
  2. 内部执行2->同步函数(堵塞线程)—> 4.

image.png
把并发队列改为串行队列则造成死锁

image.png
异步函数block中,同步函数3完成才能执行4,但是在同步队列中又要完成任务4就是大的block完成才可以执行任务3,相当于任务3和任务4都要等对方完成它才能走
去掉任务4还是崩溃,因为这个大的block无法执行,和是否执行任务无关,同步函数把线程锁住了。

image.png

3. 队列和函数的原理分析

我们知道队列分4种,主队列全局队列串行队列并发队列。但是实际上就是2种,上面也分析了。那么队列是怎么实现的呢?我们需要查看libdispatch源码下载链接

3.1 主队列

DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW

dispatch_queue_main_t

dispatch_get_main_queue(void)

{

return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);

}
复制代码

image.png
主队列是自动创建的,调用main()之前的主线程,主队列是用来在应用程序上下文中进行交互的。
继续看下DISPATCH_GLOBAL_OBJECT定义的宏

#define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
复制代码

第一个参数是类型,我们传的是dispatch_queue_main_t,第二参数是对象我们传的是_dispatch_main_q。由于主队列在main()函数之前就创建好了。我们
搜索_dispatch_main_q = 的创建的地方

// 6618342 Contact the team that owns the Instrument DTrace probe before

//         renaming this symbol

//静态变量修饰表示只会创建一次,是结构体类型

struct dispatch_queue_static_s _dispatch_main_q = {

DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),//

#if !DISPATCH_USE_RESOLVERS

.do_targetq = _dispatch_get_default_queue(true),

#endif

.dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |

DISPATCH_QUEUE_ROLE_BASE_ANON,//状态

.dq_label = "com.apple.main-thread",//名字

.dq_atomic_flags = DQF_THREAD_BOUND | DQF_WIDTH(1),//标识

.dq_serialnum = 1,//编号

};
复制代码
  • 我们还有一种便捷的方法

image.png
我们打印4种队列,其中<#const char * _Nullable label#>就是队列的名字,所以主队列应该就是默认的com.apple.main-thread的主队列名字,去libdispatch搜索

image.png
和我们之前分析的一样。

3.2 全局队列

image.png
全局队列是一个并发队列,通常传入一个队列的优先级,和flag(线程过载)标识。

dispatch_queue_global_t

dispatch_get_global_queue(intptr_t priority, uintptr_t flags)

{

dispatch_assert(countof(_dispatch_root_queues) ==

DISPATCH_ROOT_QUEUE_COUNT);


if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {

return DISPATCH_BAD_INPUT;//带有 overcommit 的队列表示每当有任务提交时,系统都会新开一个线程处理,这样就不会造成某个线程过载(overcommit)。

}

dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);//获取优先级

#if !HAVE_PTHREAD_WORKQUEUE_QOS

if (qos == QOS_CLASS_MAINTENANCE) {

qos = DISPATCH_QOS_BACKGROUND;//要求这些工作优先于其他工作运行。使用这个QOS类表明工作应该以最节能和最高效的方式运行。

} else if (qos == QOS_CLASS_USER_INTERACTIVE) {

qos = DISPATCH_QOS_USER_INITIATED;//这类工作的优先级要求低于关键的用户交互工作,但相对高于系统上的其他工作。这不是用于大型任务的节能QOS类。它的使用应该限制在足够短的持续时间内,这样用户在等待结果时就不太可能切换任务。典型的用户发起的工作将通过显示占位符内容或模态用户界面来指示进度。

}

#endif

if (qos == DISPATCH_QOS_UNSPECIFIED) {

return DISPATCH_BAD_INPUT;

}

return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);

}
复制代码

继续_dispatch_get_root_queue

DISPATCH_ALWAYS_INLINE DISPATCH_CONST

static inline dispatch_queue_global_t

_dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)

{

if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {

DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");//优先级在范围内

}

return &_dispatch_root_queues[2 * (qos - 1) + overcommit];

}
复制代码

系统创建队列4-15

struct dispatch_queue_global_s _dispatch_root_queues[] = {

#define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \

((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \

DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \

DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)

#define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \

[_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \

DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \

.dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \

.do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \

.dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \

.dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \

_dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \

_dispatch_priority_make(DISPATCH_QOS_##n, 0)), \

__VA_ARGS__ \

}

_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,

.dq_label = "com.apple.root.maintenance-qos",

.dq_serialnum = 4,

),

_DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.maintenance-qos.overcommit",

.dq_serialnum = 5,

),

_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,

),

_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,

.dq_label = "com.apple.root.default-qos",

.dq_serialnum = 10,

),

_DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,

DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.default-qos.overcommit",

.dq_serialnum = 11,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,

.dq_label = "com.apple.root.user-initiated-qos",

.dq_serialnum = 12,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.user-initiated-qos.overcommit",

.dq_serialnum = 13,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,

.dq_label = "com.apple.root.user-interactive-qos",

.dq_serialnum = 14,

),

_DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,

.dq_label = "com.apple.root.user-interactive-qos.overcommit",

.dq_serialnum = 15,

),

};
复制代码

3.3 串行队列和并发队列

3.3.1 dispatch_queue_t

日常开发中使用不论是串行队列还是并发队列都是dispatch_queue_t类型。

typedef struct dispatch_queue_s *dispatch_queue_t;//是dispatch_queue_s类型的结构体
//继续查看
#define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

_DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \

/* LP64 global queue cacheline boundary */ \

unsigned long dq_serialnum; \

const char *dq_label; \

DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \

const uint16_t dq_width, \

const uint16_t __dq_opaque2 \

); \

dispatch_priority_t dq_priority; \

union { \

struct dispatch_queue_specific_head_s *dq_specific_head; \

struct dispatch_source_refs_s *ds_refs; \

struct dispatch_timer_source_refs_s *ds_timer_refs; \

struct dispatch_mach_recv_refs_s *dm_recv_refs; \

struct dispatch_channel_callbacks_s const *dch_callbacks; \

}; \

int volatile dq_sref_cnt


struct dispatch_queue_s {

DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);

/* 32bit hole on LP64 */

} DISPATCH_ATOMIC64_ALIGN;
//主要是_DISPATCH_QUEUE_CLASS_HEADER?继续查看
#if OS_OBJECT_HAVE_OBJC1

#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

DISPATCH_OBJECT_HEADER(x); \

DISPATCH_UNION_LE(uint64_t volatile dq_state, \

dispatch_lock dq_state_lock, \

uint32_t dq_state_bits \

); \

__pointer_sized_field__

#else

#define _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \

DISPATCH_OBJECT_HEADER(x); \

__pointer_sized_field__; \

DISPATCH_UNION_LE(uint64_t volatile dq_state, \

dispatch_lock dq_state_lock, \

uint32_t dq_state_bits \

)
//继续查看DISPATCH_OBJECT_HEADER ?
#define DISPATCH_OBJECT_HEADER(x) \

struct dispatch_object_s _as_do[0]; \

_DISPATCH_OBJECT_HEADER(x)

\
//继续查看_DISPATCH_OBJECT_HEADER?
#endif
#define _DISPATCH_OBJECT_HEADER(x) \

struct _os_object_s _as_os_obj[0]; \

OS_OBJECT_STRUCT_HEADER(dispatch_##x); \

struct dispatch_##x##_s *volatile do_next; \

struct dispatch_queue_s *do_targetq; \

void *do_ctxt; \

union { \

dispatch_function_t DISPATCH_FUNCTION_POINTER do_finalizer; \

void *do_introspection_ctxt; \

}

//继续查看OS_OBJECT_STRUCT_HEADER?
#define OS_OBJECT_STRUCT_HEADER(x) \

_OS_OBJECT_HEADER(\

const void *_objc_isa, \

do_ref_cnt, \

do_xref_cnt); \

const struct x##_vtable_s *do_vtable
//继续查看_OS_OBJECT_HEADER?
#define _OS_OBJECT_HEADER(isa, ref_cnt, xref_cnt) \

        isa; /* must be pointer-sized and use __ptrauth_objc_isa_pointer */ \

        int volatile ref_cnt; \

        int volatile xref_cnt
复制代码

经过剥茧抽丝最终找到dispatch_queue_t结构体储存来我们定义的lablepriorityspecific等以及isa引用计数等。

3.3.2 dispatch_queue_create

我们上面知道了队列本质是结构体,储存了我们定义的labelpriority等信息。继续看下怎么创建的

dispatch_queue_t

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)

{

return _dispatch_lane_create_with_target(label, attr,

DISPATCH_TARGET_QUEUE_DEFAULT, true);

}
DISPATCH_NOINLINE

static dispatch_queue_t

_dispatch_lane_create_with_target(const char *label, dispatch_queue_attr_t dqa,

dispatch_queue_t tq, bool legacy)

{

dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);

\


//

// Step 1: Normalize arguments (qos, overcommit, tq)

//处理规范化参数,优先级,线程过载,队列类型

\


dispatch_qos_t qos = dqai.dqai_qos;//处理优先级

#if !HAVE_PTHREAD_WORKQUEUE_QOS

if (qos == DISPATCH_QOS_USER_INTERACTIVE) {

dqai.dqai_qos = qos = DISPATCH_QOS_USER_INITIATED;

}

if (qos == DISPATCH_QOS_MAINTENANCE) {

dqai.dqai_qos = qos = DISPATCH_QOS_BACKGROUND;

}

#endif // !HAVE_PTHREAD_WORKQUEUE_QOS

\


_dispatch_queue_attr_overcommit_t overcommit = dqai.dqai_overcommit;

if (overcommit != _dispatch_queue_attr_overcommit_unspecified && tq) {

if (tq->do_targetq) {

DISPATCH_CLIENT_CRASH(tq, "Cannot specify both overcommit and "

"a non-global target queue");

}

}

//处理队列类型 优先级下是否线程过载重新开辟新的线程

if (tq && dx_type(tq) == DISPATCH_QUEUE_GLOBAL_ROOT_TYPE) {

// Handle discrepancies between attr and target queue, attributes win

if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {

if (tq->dq_priority & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) {

overcommit = _dispatch_queue_attr_overcommit_enabled;

} else {

overcommit = _dispatch_queue_attr_overcommit_disabled;

}

}

if (qos == DISPATCH_QOS_UNSPECIFIED) {

qos = _dispatch_priority_qos(tq->dq_priority);

}

tq = NULL;

} else if (tq && !tq->do_targetq) {

// target is a pthread or runloop root queue, setting QoS or overcommit

// is disallowed

if (overcommit != _dispatch_queue_attr_overcommit_unspecified) {

DISPATCH_CLIENT_CRASH(tq, "Cannot specify an overcommit attribute "

"and use this kind of target queue");

}

} else {

if (overcommit == _dispatch_queue_attr_overcommit_unspecified) {

// Serial queues default to overcommit!

overcommit = dqai.dqai_concurrent ?

_dispatch_queue_attr_overcommit_disabled :

_dispatch_queue_attr_overcommit_enabled;

}

}

if (!tq) {

tq = _dispatch_get_root_queue(

qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,

overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;

if (unlikely(!tq)) {

DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");

}

}



//

// Step 2: Initialize the queue

//

\


if (legacy) {

// if any of these attributes is specified, use non legacy classes

if (dqai.dqai_inactive || dqai.dqai_autorelease_frequency) {

legacy = false;

}

}

\


const void *vtable;//拼接队列名称

dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;

if (dqai.dqai_concurrent) {

vtable = DISPATCH_VTABLE(queue_concurrent);//队列类型

} else {

vtable = DISPATCH_VTABLE(queue_serial);

}

switch (dqai.dqai_autorelease_frequency) {

case DISPATCH_AUTORELEASE_FREQUENCY_NEVER:

dqf |= DQF_AUTORELEASE_NEVER;

break;

case DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM:

dqf |= DQF_AUTORELEASE_ALWAYS;

break;

}

if (label) {//拼接队列名字

const char *tmp = _dispatch_strdup_if_mutable(label);

if (tmp != label) {

dqf |= DQF_LABEL_NEEDS_FREE;

label = tmp;

}

}

\


dispatch_lane_t dq = _dispatch_object_alloc(vtable,

sizeof(struct dispatch_lane_s));//开辟队列内存

_dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?

DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |

(dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));//队列初始化

\


dq->dq_label = label;//赋值label队列名字

dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,

dqai.dqai_relpri);//赋值优先级

if (overcommit == _dispatch_queue_attr_overcommit_enabled) {

dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;//赋值

}

if (!dqai.dqai_inactive) {

_dispatch_queue_priority_inherit_from_target(dq, tq);

_dispatch_lane_inherit_wlh_from_target(dq, tq);

}

_dispatch_retain(tq);

dq->do_targetq = tq;//绑定关系

_dispatch_object_debug(dq, "%s", __func__);

return _dispatch_trace_queue_create(dq)._dq;//队列处理

}
typedef struct dispatch_queue_attr_info_s {

dispatch_qos_t dqai_qos : 8;

int      dqai_relpri : 8;

uint16_t dqai_overcommit:2;

uint16_t dqai_autorelease_frequency:2;

uint16_t dqai_concurrent:1;

uint16_t dqai_inactive:1;

} dispatch_queue_attr_info_t;

\
复制代码

主要分为2步

  1. 第一步:规范化参数处理队列优先级(有6个:userInteractive>default>unspecified>userInitiated>utility>background),以及过载线程(支持overcommit的队列在创建队列时无论系统是否有足够的资源都会重新开一个线程)。
  2. 第二步 :处理队列名字,队列类型(串行还是并行),初始化队列绑定关系,队列处理
  • 【第一步】dqai创建

image.png

//
typedef struct dispatch_queue_attr_info_s {

dispatch_qos_t dqai_qos : 8;

int      dqai_relpri : 8;

uint16_t dqai_overcommit:2;

uint16_t dqai_autorelease_frequency:2;

uint16_t dqai_concurrent:1;

uint16_t dqai_inactive:1;

} dispatch_queue_attr_info_t;

复制代码

主要是创建dispatch_queue_attr_info_t类型用来储存队列的优先级,过载线程等信息。

  • 【第二步】设置队列相关联的属性,例如队列优先级qos
  • 【第三步】通过DISPATCH_VTABLE拼接队列名称
  • 【第四步】初始化alloc + init,队列类型设置,isa指向,DQF_WIDTH(width)类型

alloc:

void *

_dispatch_object_alloc(const void *vtable, size_t size)

{

#if OS_OBJECT_HAVE_OBJC1

const struct dispatch_object_vtable_s *_vtable = vtable;

dispatch_object_t dou;

dou._os_obj = _os_object_alloc_realized(_vtable->_os_obj_objc_isa, size);//isa指向

dou._do->do_vtable = vtable;//设置类型

return dou._do;

#else

return _os_object_alloc_realized(vtable, size);

#endif

}
复制代码

init:

static inline dispatch_queue_class_t

_dispatch_queue_init(dispatch_queue_class_t dqu, dispatch_queue_flags_t dqf,

uint16_t width, uint64_t initial_state_bits)

{

uint64_t dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(width);

dispatch_queue_t dq = dqu._dq;



dispatch_assert((initial_state_bits & ~(DISPATCH_QUEUE_ROLE_MASK |

DISPATCH_QUEUE_INACTIVE)) == 0);


if (initial_state_bits & DISPATCH_QUEUE_INACTIVE) {

dq->do_ref_cnt += 2; // rdar://8181908 see _dispatch_lane_resume

if (dx_metatype(dq) == _DISPATCH_SOURCE_TYPE) {

dq->do_ref_cnt++; // released when DSF_DELETED is set

}

}


dq_state |= initial_state_bits;

dq->do_next = DISPATCH_OBJECT_LISTLESS;

dqf |= DQF_WIDTH(width);//串行还是并发width本身就是并发数的个数,对于串行队列是1而对于并发队列是不限制的

os_atomic_store2o(dq, dq_atomic_flags, dqf, relaxed);

dq->dq_state = dq_state;

dq->dq_serialnum =

os_atomic_inc_orig(&_dispatch_queue_serial_numbers, relaxed);

return dqu;

}

复制代码
  • 【第五步】 创建的队列进行处理_dispatch_trace_queue_create进行一些处理实际上_dispatch_introspection_queue_create
dispatch_queue_class_t

_dispatch_introspection_queue_create(dispatch_queue_t dq)

{

dispatch_queue_introspection_context_t dqic;

size_t sz = sizeof(struct dispatch_queue_introspection_context_s);//计算大小


if (!_dispatch_introspection.debug_queue_inversions) {

sz = offsetof(struct dispatch_queue_introspection_context_s,

__dqic_no_queue_inversion);

}

dqic = _dispatch_calloc(1, sz);

dqic->dqic_queue._dq = dq;

if (_dispatch_introspection.debug_queue_inversions) {

LIST_INIT(&dqic->dqic_order_top_head);

LIST_INIT(&dqic->dqic_order_bottom_head);

}

dq->do_introspection_ctxt = dqic;


_dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);

LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);

_dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);


DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);

if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {

_dispatch_introspection_queue_create_hook(dq);

}

return upcast(dq)._dqu;

}
复制代码

_dispatch_introspection_queue_create_hook -> dispatch_introspection_queue_get_info -> _dispatch_introspection_lane_get_info中可以看出,与我们自定义的类还是有所区别的,创建队列在底层的实现是通过模板创建

static inline dispatch_introspection_queue_s

_dispatch_introspection_workloop_get_info(dispatch_workloop_t dwl)

{

uint64_t dq_state = os_atomic_load2o(dwl, dq_state, relaxed);

dispatch_introspection_queue_s diq = {

.queue = dwl->_as_dq,

.target_queue = dwl->do_targetq,

.label = dwl->dq_label,

.serialnum = dwl->dq_serialnum,

.width = 1,

.suspend_count = 0,

.enqueued = _dq_state_is_enqueued(dq_state),

.barrier = _dq_state_is_in_barrier(dq_state),

.draining = 0,

.global = 0,

.main = 0,

};

return diq;

}
复制代码

3.4 同步函数原理分析

源码如下:
image.png
继续查看

image.png

DISPATCH_ALWAYS_INLINE

static inline void

_dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,

dispatch_function_t func, uintptr_t dc_flags)

{

if (likely(dq->dq_width == 1)) {//同步函数

return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);//栅栏函数初始化

}


if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {

DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");

}


dispatch_lane_t dl = upcast(dq)._dl;

// Global concurrent queues and queues bound to non-dispatch threads

// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE

if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {

return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);//死锁

}


if (unlikely(dq->do_targetq->do_targetq)) {

return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);

}

_dispatch_introspection_sync_begin(dl);//处理当前信息

_dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(

_dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));//并发队列则调用

}
复制代码

流程是什么呢?我们添加对应的符号断点进行查看
image.png
查看下是否width ==1 则是同步函数调用_dispatch_barrier_sync_f并发队列则调用_dispatch_sync_invoke_and_complete。先看下_dispatch_barrier_sync_f

image.png
先获取线程id处理死锁的情况,因次先查看下死锁的情况:

image.png
继续看__DISPATCH_WAIT_FOR_QUEUE__

image.png
进入_dq_state_drain_locked_by
image.png
继续进入
image.png
判断将要调度的当前等待的队列是不是同一个,如果相同则返回YES,产生死锁DISPATCH_CLIENT_CRASH。如果没有产生死锁,则执行 _dispatch_trace_item_pop()出队列执行。如何执行调度呢,需要看一下_dispatch_sync_invoke_and_complete_recurse

image.png
查看下_dispatch_sync_function_invoke_inline
image.png
查看下callout

image.png
可以比较清楚的看到最终执行f函数,这个就是外界传过来的回调block
同步函数主要是三个步骤

  • 将任务压入队列: _dispatch_thread_frame_push
  • 执行任务的block回调: _dispatch_client_callout
  • 将任务出队:_dispatch_thread_frame_pop

3.5 异步函数原理分析

void

dispatch_async(dispatch_queue_t dq, dispatch_block_t work)

{

dispatch_continuation_t dc = _dispatch_continuation_alloc();

uintptr_t dc_flags = DC_FLAG_CONSUME;

dispatch_qos_t qos;

qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);//处理block回调 work

_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//并发处理

}
复制代码

查看任务包装_dispatch_continuation_init

image.png
我们如何确定走的那个return,继续用符号断点

image.png
继续查看_dispatch_continuation_init_f

image.png
保存funcdispatch_continuation_t中,在把dc赋值到dispatch_qos_t中。

  • _dispatch_continuation_async 处理

image.png
核心是dx_push宏定义,查看

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
复制代码

本质是调用dx_vtabledq_push(其实就是调用对象的do_push),进一步查看 dq_push

image.png
不同的队列执行不同的dq_push,我们代码中队列用的全局队列,添加符号断点验证下。

image.png
继续查看_dispatch_root_queue_push

image.png
查看
image.png

我们可以看到,我们装入到自定义的任务都被扔到其挂靠的root队列里去了,因此我们我们自己创建的队列只是一个代理人身份,继续查看 _dispatch_root_queue_poke

image.png
查看_dispatch_root_queue_poke_slow

image.png
先看下_dispatch_root_queues_init的实现:

static inline void

_dispatch_root_queues_init(void)

{

dispatch_once_f(&_dispatch_root_queues_pred, NULL,

_dispatch_root_queues_init_once);

}

复制代码

dispatch_once_f是单例,具体实现后面再看。_dispatch_root_queues_init_once的实现

image.png
查看下_dispatch_workloop_worker_thread

image.png
查看 _dispatch_worker_thread2

image.png
查看 _dispatch_root_queue_drain
image.png
查看 _dispatch_continuation_pop_inline

image.png
首先看了有没有vtable(_dispatch_object_has_vtable),这里解释了为什么dispatch_barrier_async尽管主要流程和dispatch_async一模一样但是无法应用到全局队列的原因,因为全局队列没有v_table结构会直接像dispatch_async一样执行
查看_dispatch_continuation_invoke_inline

image.png
真正执行block回调,查看_dispatch_client_callout,上面在_dispatch_continuation_init中对block进行包装,它在dx_push调用时包装进了qos

image.png
我们也可以打印下堆栈信息验证下流程

image.png
其block回调执行的调用路径为:_dispatch_root_queues_init_once ->_dispatch_worker_thread2 -> _dispatch_root_queue_drain -> _dispatch_root_queue_drain -> _dispatch_continuation_pop_inline -> _dispatch_continuation_invoke_inline -> _dispatch_client_callout -> dispatch_call_block_and_release
异步函数的底层较为复杂,其中的分支比较多,但是思路还是比较明确的,用一个链表保存所有的block,在队列本身维护了一个链表新加入的block放在后面,先进先出,之后在底层的线程池中,依次取出block并执行。

4. 总结

  • GCD依赖于队列,队列分为串行队列并发队列,常用的4种队列,系统创建的主队列(系统创建队列1)和全局队列(系统创建队列4-15),队列所占内存大小一般会有大概64*1024大小。
  • 函数有2种,队列有有2种因此他们有4种组合大的方向,同步函数会堵塞队列,异步函数不会,串行队列FIFO先进先出,并发队列可以执行多个任务,不需要等待。
  • 死锁本质是线程执行block回调的队列和在等待的队列同一个队列造成死锁崩溃。
  • 队列本质是dispatch_queue_t的结构体对象,我们定义的lablepriorityspecific等以及isa引用计数,队列创建大致2步,第一步:规范化参数处理队列优先级,支持overcommit的队列在创建队列时无论系统是否有足够的资源都会重新开一个线程;第二步:线程名字,类型处理,线程初始化,队列处理。
  • 同步函数原理主要3步,任务加入队列,执行任务回调,任务出列
  • 异步函数原理:队列本身用一个链表保存所有的任务block,遵循先进先出,之后在底层线程池依次取出block执行
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享