函数与队列 和gcd原理分析(上)

GCD 介绍

什么是GCD?

全称是 Grand Central Dispatch 

纯 C 语⾔,提供了⾮常多强⼤的函数

将任务添加到队列,并且指定执行任务的函数。

优势

GCD 是苹果公司为多核的并⾏运算提出的解决⽅案

GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)

GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)

程序员只需要告诉 GCD 想要执⾏什么任务,不需要编写任何线程管理代码

GCD 的使用步骤 

 创建/获取队列:创建/获取一个并发/串行队列;

 创建任务:确定要做的事; 

 将任务添加进队列中(同时指定任务的执行方式):GCD 会自动将队列中的任务取出,放到对应的线程中执行;任务的取出遵循队列的 FIFO 原则:先进先出,后进后出;GCD 中,要执行队列中的任务时,会自动开启一个线程,当任务执行完,线程不会立刻销毁,而是放到了线程池中。如果接下来还要执行任务的话就从线程池中取出线程,这样节省了创建线程所需要的时间。但如果一段时间内没有执行任务的话,该线程就会被销毁,再执行任务就会创建新的线程。

 函数

* 任务使⽤ block 封装

* 任务的 block 没有参数也没有返回值

* 执⾏任务的函数

* 异步 `dispatch_async` 

* 不⽤等待当前语句执⾏完毕,就可以执⾏下⼀条语句

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

* 异步是多线程的代名词

* 同步 `dispatch_sync` 

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

* 不会开启线程

* 在当前执⾏ block 的任务


函数与队列

同步函数串行队列 

不会开启线程,在当前线程执行任务

任务串行执行,任务一个接着一个

会产生堵塞

同步函数并发队列 

不会开启线程,在当前线程执行任务

任务一个接着一个

异步函数串行队列 

开启一条新线程

任务一个接着一个

异步函数并发队列 

开启线程,在当前线程执行任务

任务异步执行,没有顺序,CPU调度有关

主队列与全局并发队列

主队列

专门用来在主线程上调度任务的串行队列

不会开启线程

如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

dispatch_get_main_queue()

全局并发队列

为了方便程序员的使用,苹果提供了全局并发队列dispatch_get_global_queue(0, 0)

全局队列是一个并发队列

在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

死锁现象

主线程因为你同步函数的原因等着先执行任务

主队列等着主线程的任务执行完毕再执行自己的任务

主队列和主线程相互等待会造成死锁

GCD耗能问题

创建函数,查看执行完毕时间,代码如下:



分别开启dispatch_sync和dispatch_async进行打印运行时间。结果如下:

dispatch_sync: 0.000005

dispatch_async: 0.0000010

这是因为同步和异步都是耗费时间的。都是需要调度的,但是使用这种方式解决的就是并发执行的问题。

dispatch_async

运行查看打印结果为 1 - 5 - 2 - 4 - 3,

首先执行1,接下来会执行dispatch_async,但他是异步执行,也就是说不会堵塞当前主线程,并且dispatch_async的任务与调度的时间足够快时,2可能会比5先执行,也就是说当前除了1的执行顺序是准确之外,2会在1之后,3会在2之后之外,4与5可以在任何位置都是有可能的。

dispatch_sync

打印顺序为:1 - 5 - 2 - 3 - 4,因为dispatch_sync是同步函数,需要等待3执行完成后才能执行4

将DISPATCH_QUEUE_CONCURRENT修改为DISPATCH_QUEUE_SERIAL后再运行。结果为:1 - 5 - 2 - 死锁,因为在串行队列中,dispatch_sync会等待dispatch_async中的任务完成后再执行,但它会阻塞当前队列。如图

当前的dispatch_sync块需要等待3执行完毕后才能执行4

但是因为串行队列的FIFO原则,3需要等待dispatch_sync块执行完毕后才能执行

因此出现死锁

dispatch_async与dispatch_sync


首先quque是并发队列,因此1 2 3没有顺序

但是3会进行堵塞主线程,因此0在3之后

然后7 8 9也没有顺序

GCD的底层原理

1、 队列的种类

队列分为串行队列和并行队列。

串行队列 dispatch_queue_create("xh", DISPATCH_QUEUE_SERIAL);

并行队列 dispatch_queue_create("xh", DISPATCH_QUEUE_CONCURRENT);

通过符号断点查看可知在libdispatch.dylib框架中进行了调度。

libdispatch.dylib源码分析

打印结果:


由结果可知:com.apple.main-thread的label,因为在创建队列时都会给一个label进行创建,如果没有,肯定在系统中存在默认值,因此在libdispatch.dylib框架中进行搜索查看。源码如下:

以上就是主队列创建的信息。

在注释中我们可以知道,主队列执行在main()函数之前,并且在libdispatch.dylib中一定存在init()方法,搜索查找该方法,名称为libdispatch_init,简化源码如下


从该源码中可以看到_dispatch_main_q字样

// 设置当前主队列_dispatch_queue_set_current(&_dispatch_main_q);

// 绑定线程_dispatch_queue_set_bound_thread(&_dispatch_main_q);

再研究一下全局并发队列

全局搜索com.apple.root.default-qos


查看DISPATCH_QUEUE_WIDTH_POOL源码: 


DISPATCH_QUEUE_WIDTH_POOL 是全局并发队列的最大容积 – 4095

DISPATCH_QUEUE_WIDTH_MAX是普通并发队列的最大容积 – 4094

其中相差为1是主队列已经创建。

队列的创建

查看dispatch_queue_create函数,源码如下

查看_dispatch_lane_create_with_target函数的源码,如下:


在这个函数中返回的是_dq,因此查看_dispatch_trace_queue_create函数的内容。

查看_dispatch_introspection_queue_create函数源码,如下:

其中dqic->dqic_queue._dq = dq;为创建dq的方法,dqic同样在当前函数中进行创建。

由上方源码可知,在创建时,返回的dq只是进行了一层包装。我们只要研究dq就可以了

我们回到_dispatch_lane_create_with_target函数,

这里是创建dq 通过dqai.dqai_concurrent判断是并发还是串行 dqai是怎么来的呢

再往上看:

这里创建了dqai

查看_dispatch_queue_attr_to_info的源码


该函数是对dqai的一些赋值,不需要真正的理解。

查看queue的名称

可以看到串行队列的名字为OS_dispatch_queue_serial,并发队列的名字为OS_dispatch_queue_concurrent.

全局搜索这两个名字发现都是名字定义,并没有isa的指向代码。

因此查看DISPATCH_VTABLE()方法。

查看DISPATCH_OBJC_CLASS函数源码如下

查看DISPATCH_CLASS_SYMBOL的内容

查看DISPATCH_CLASS的内容

从此处源码可以知道,queue的名字是使用OS_dispatch_...拼接的名字。

因此在创建时,并发队列使用的是queue_concurrent,串行队列使用的是queue_serial字符串。

拼接之后的名称与打印的名称一致。

queue的isa的创建

 _dispatch_object_alloc源码查看

我们需要查看OS_OBJECT_HAVE_OBJC2的内容,即else中_os_object_alloc_realized的内容。

从上方源码可知isa的创建。

总结

queue是一个对象。

queue创建流程: dispatch_queue_create => _dispatch_object_alloc => _os_object_alloc_realized

DISPATCH_QUEUE_WIDTH_POOL 是并发队列的最大容积 – 4095

DISPATCH_QUEUE_WIDTH_MAX是普通队列的最大容积 – 4094

异步函数 dispatch_async


查看_dispatch_continuation_init函数。源码如下:


查看堆栈

查看_dispatch_worker_thread2函数。源码如下:


查看_dispatch_root_queue_drain


查看_dispatch_continuation_pop_inline函数,源码如下

查看_dispatch_continuation_invoke_inline源码。

查看_dispatch_client_callout函数源码,如下:

最后再进行_dispatch_call_block_and_release处理  

流程完毕~~

下篇继续dispatch_async讲解调用流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281