当前位置: 首页 > 编程日记 > 正文

GCD 容易让人迷惑的几个小问题

来源:涂耀辉 

链接:http://www.jianshu.com/p/ff444d664e51



写在开头:


本文旨在阐述一些大家容易产生迷惑的GCD相关内容,如果是需要了解一些GCD概念或者基础用法,可以看看这两篇文章:GCD 扫盲篇、巧谈GCD 。


目录:

迷惑一:队列和线程的关系

迷惑二:GCD的死锁

迷惑三:以下这些API的异同与作用场景:

dispatch_async、dispatch_sync、dispatch_barrier_async、dispatch_barrier_sync


迷惑一:队列和线程的关系


错误理解:


  • 有些人会产生一种错觉,觉得队列就是线程。又有些人会有另外一种错觉,一个追加Block就是一个线程。


正确理解:


对我们使用者来说,与其说GCD是面向线程的,不如说是面向队列的。 它隐藏了内部线程的调度。


  • 我们所做的仅仅是创建不同的队列,把Block追加到队列中去执行,而队列是FIFO(先进先出)的。

  • 它会按照我们追加的Block的顺序,在综合我们调用的gcd的api(sync、async、dispatch_barrier_async等等),以及根据系统负载来增减线程并发数, 来调度线程执行Block。


我们来看看以下几个例子:


例一:我们在主线程中,往一个并行queue,以sync的方式提交了一个Block,结果Block在主线程中执行。


dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue1, ^{

    NSLog(@"%@",[NSThread currentThread]);

});


输出结果:{number = 1, name = main}


例二:我们在主线程中用aync方式提交一个Block,结果Block在分线程中执行。


dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue1, ^{

    NSLog(@"%@",[NSThread currentThread]);

});


输出结果:{number = 2, name = (null)}


例三:我们分别用sync和async向主队列提交Block,结果Block都是在主线程中执行:


注意:我们不能直接在主线程用sync如下的形式去提交Block,否则会引起死锁:


dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"%@",[NSThread currentThread]);

});


我们用sync如下的方式去提交Block:


dispatch_queue_t queue1 = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue1, ^{

    dispatch_sync(dispatch_get_main_queue(), ^{

        NSLog(@"%@",[NSThread currentThread]);

    });

});


输出结果:{number = 1, name = main}


dispatch_async(dispatch_get_main_queue(), ^{

    NSLog(@"%@",[NSThread currentThread]);

});


输出结果:{number = 1, name = main}


总结一下:


  • 往主队列提交Block,无论是sync,还是async,都是在主线程中执行。

  • 往非主队列中提交,如果是sync,会在当前提交Block的线程中执行。如果是async,则会在分线程中执行。


上文需要注意以下两点:


  • 这里的sync,async并不局限于dispatch_sync、dispatch_async,而指的是GCD中所有同步异步的API。

  • 这里我们用的是并行queue,如果用串行queue,结果也是一样的。唯一的区别是并行queue会权衡当前系统负载,去同时并发几条线程去执行Block,而串行queue中,始终只会在同一条线程中执行Block。


迷惑二:GCD的死锁


因为很多人因为不理解发生死锁的原因,所以导致从不会去用sync相关的API,而sync的应用场景还是非常多的,我们不能因噎废食,所以我们了解死锁原理还是很重要的。


简单举个死锁例子:


dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"任务一");

});

NSLog(@"任务二");


首先造成死锁的原因很简单,两个任务间互相等待。

为了加深大家的理解,在这里我们尽可能用最详尽,同时也有点绕的方式总结下这个死锁的流程:


  • 如上,在主线程中,往主队列同步提交了任务一。因为往queue中提交Block,总是追加在队列尾部的,而queue执行Block的顺序为先进先出(FIFO),所以任务一需要在当前队列它之前的任务(任务二)全部执行完,才能轮到它。

  • 而任务二因为任务一的sync,被阻塞了,它需要等任务一执行完才能被执行。两者互相等待对方执行完,才能执行,程序被死锁在这了。

  • 这里需要注意这里死锁的很重要一个条件也因为主队列是一个串行的队列(主队列中只有一条主线程)。如果我们如下例,在并行队列中提交,则不会造成死锁:


dispatch_async(dispatch_get_global_queue(0, 0), ^{

  dispatch_sync(dispatch_get_global_queue(0, 0), ^{

      NSLog(@"任务一");

  });

  NSLog(@"任务二");

});


原因是并行队列中任务一虽被提交仍然是在queue的队尾,在任务二之后,但是因为是并行的,所以任务一并不会一直等任务二结束才去执行,而是直接执行完。此时任务二的因为任务一的结束,sync阻塞也就消除,任务二得以执行。


上述第一个死锁的例子,我们很简单的改写一下,死锁就被消除了:


dispatch_sync(dispatch_get_global_queue(0, 0), ^{

    NSLog(@"任务一");

});

NSLog(@"任务二");


我们在主线程中,往全局队列同步提交了Block,因为全局队列和主队列是两个队列,所以任务一的执行,并不需要等待任务二。所以等任务一结束,任务二也可以被执行。

当然这里因为提交Block所在队列,Block被执行的队列是完全不同的两个队列,所以这里用串行queue,也是不会死锁的。大家可以自己写个例子试试,这里就不赘述了。


看到这我们可以知道一些sync的阻塞机制:


  • sync提交Block,首先是阻塞的当前提交Block的线程(简单理解下就是阻塞sync之后的代码)。例如我们之前举的例子中,sync总是阻塞了任务二的执行。

  • 而在队列中,轮到sync提交的Block,仅仅阻塞串行queue,而不会阻塞并行queue。(dispatch_barrier_(a)sync除外,我们后面会讲到。)


我们了解了sync的阻塞机制,再结合发生死锁的根本原因来自于互相等待,我们用下面一句话来总结一下,会引起GCD死锁的行为:

如果同步(sync)提交一个Block到一个串行队列,而提交Block这个动作所处的线程,也是在当前队列,就会引起死锁。


我相信,如果看明白了上述所说的,基本上可以放心的使用sync相关api,而不用去担心死锁的问题。

关于更多死锁例子这里就不写了,基本上都是基于上述所说的,只是在不同队列中,sync,async组合形式不同,但是原理都是和上述一样。如果实在感兴趣的,可以看看这篇文章:一篇专题让你秒懂GCD死锁问题!


迷惑三:以下4个GCD方法的区别:


dispatch_async(, )

dispatch_sync(, )

dispatch_barrier_async(, )

dispatch_barrier_sync(, )


1)dispatch_async 这个就不用说了,估计大家都用的非常熟悉。

2)dispatch_barrier_async, 这个想必大家也知道是干嘛用的,如果不知道,我也大概讲讲:

它的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。当然它的作用导致它只有在并行队列中有意义。


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

dispatch_async(queue, ^{

    // 任务1

    ...

});

dispatch_async(queue, ^{

    // 任务2

    ...

});

dispatch_async(queue, ^{

    // 任务3

    ...

});

dispatch_barrier_async(queue, ^{

    // 任务4

    ...

});

dispatch_async(queue, ^{

    // 任务5

    ...

});

dispatch_async(queue, ^{

    // 任务6

    ...

});


例如上述任务,任务1,2,3的顺序不一定,4在中间,最后是5,6任务顺序不一定。它就像一个栅栏一样,挡在了一个并行队列中间。

当然这里有一点需要注意的是:dispatch_barrier_(a)sync只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。


我们讲到这,顺便来讲讲它的用途,例如我们在一个读写操作中:

我们要知道一个数据,读与读之间是可以用线程并行的,但是写与写、写与读之间,就必须串行同步或者使用线程锁来保证线程安全。但是我们有了dispatch_barrier_async,我们就可以如下使用:


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

dispatch_async(queue, ^{

    //读操作

});

dispatch_async(queue, ^{

    // 读操作

});

dispatch_barrier_async(queue, ^{

    // 写操作

});

dispatch_barrier_async(queue, ^{

    // 写操作

});

dispatch_async(queue, ^{

    // 读操作

});


这样写操作的时候,始终只有它这一条线程在进行。而读操作一直是并行的。这么做充分利用了多线程的优势,还不需要加锁,减少了相当一部分的性能开销。实现了读写操作的线程安全。


3)dispatch_barrier_sync这个方法和dispatch_barrier_async作用几乎一样,都可以在并行queue中当做栅栏。

唯一的区别就是:dispatch_barrier_sync有GCD的sync共有特性,会阻塞提交Block的当前线程,而dispatch_barrier_async是异步提交,不会阻塞。


4)dispatch_sync,我们来讲讲它和dispatch_barrier_sync的区别。二者因为是sync提交,所以都是阻塞当前提交Block线程。

而它俩唯一的区别是:dispatch_sync并不能阻塞并行队列。其实之前死锁有提及过,担心大家感觉疑惑,还是写个例子:


dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue, ^{

    dispatch_async(queue, ^{

        NSLog(@"任务二");

    });

    dispatch_async(queue, ^{

        NSLog(@"任务三");

    });

    //睡眠2秒

    [NSThread sleepForTimeInterval:2];

    NSLog(@"任务一");

});


输出结果 :
任务三
任务二
任务一


很显然,并行队列没有被sync所阻塞。


而dispatch_barrier_sync可以阻塞并行队列(栅栏作用的体现):


dispatch_queue_t queue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT);

dispatch_barrier_sync(queue, ^{

    dispatch_async(queue, ^{

        NSLog(@"任务二");

    });

    dispatch_async(queue, ^{

        NSLog(@"任务三");

    });

    //睡眠2秒

    [NSThread sleepForTimeInterval:2];

    NSLog(@"任务一");

});


输出结果 :
任务一
任务二
任务三


总结一下:

这些API都是有各自应用场景的,苹果也不会给我们提供重复而且毫无意义的方法。


其中在AF的图片缓存处理中,就有大量组合的用到:

dispatch_barrier_sync、dispatch_barrier_async、dispatch_sync

这些API,主要是为了保证在不使用锁下,缓存数据的读写的线程安全。感兴趣的可以去楼主之前的文章中看看:


AFNetworking之UIKit扩展与缓存实现

http://www.jianshu.com/p/4ffeb1ba3046

相关文章:

【贪心】【codevs】1214 线段覆盖

http://codevs.cn/problem/1214/ 我去这个题。。。wa的我都没脾气了。。。 我写while(~scanf(“%d”, &n))竟然是不对的。。。 这个程序你妹多次输入是不能结束的???&#xff1f…

虚函数表剖析,网上转的,呵呵

http://www.cppblog.com/xczhang/archive/2008/01/20/41508.html C虚函数表解析(转)C中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的…

c++语言文件流,C++ IO类、文件输入输出、string流详细讲解

新的C标准中有三分之二的内容都是描述标准库。接下来重点学习其中几种核心库设施,这些是应该熟练掌握的。标准库的核心是很多容器类(顺序容器和关联容器等)和一簇泛型算法(该类算法通常在顺序容器一定范围内的元素上或其他类型的序列上进行操作)。该篇主要学习IO库。…

iOS 走近商城 APP(三 WKWebView 商品规格选择框架封装)

原文链接:http://www.jianshu.com/p/293ee1bfe104商城 —— 由 3033 分享开篇 忽然发现最近也只有值班才能写东西了,中间更新了两篇其他的断了下商城相关的文章,仔细看了看之前觉得干货太少,今天写点实际的吧,闲说少说…

c语言如何不产生僵尸进程,第三章 九析带你处理 zombie(defunct) 进程

目录1 前言2 僵尸进程2.1 进程简介2.2 僵尸进程例子2.3 僵尸进程危害3 处理僵尸进程3.1 kill 命令3.2 kill 父进程3.3 reboot3.4 magic sysrq key 方法1 前言在 centos7 跑 Docker 和 k8s 时,偶尔会出现 systemctl 失效的情况,现象如下:Faile…

音视频开发学习笔记

http://www.jianshu.com/notebooks/6409162/top

VS2010 MFC中改变static字体颜色、大小、背景颜色(自定义类),及手动关联变量的方法...

在MFC的Dialog工程中生成一个CStatic的自定义类,类名例如为:CColorStatic 定义必要的变量: protected:COLORREF m_crText; // 字体颜色COLORREF m_crBackColor; // 背景颜色HBRUSH m_hBrush; // 画刷LOGFONT m_lf; // 字体…

android 上传字符串,Android中发送Http请求(包括文件上传、servlet接收)的实例代码...

/*** 通过拼接的方式构造请求内容,实现参数传输以及文件传输* param actionUrl* param params* param files* return* throws IOException*/public static String post(String actionUrl, Map params,Map files) throws IOException {String BOUNDARY java.util.UU…

JavaScript最全编码规范

转载: JavaScript最全编码规范 类型 ●基本类型:访问基本类型时,应该直接操作类型值 ●string ●number ●boolean ●null ●undefined var foo 1; var bar foo; bar 9; console.log(foo, bar); // > 1, 9 ●复合类型:访问复合类型时&a…

源码推荐:collectionView拖拽,仿凤凰FM iOS 局部监听键盘再也不会挡住输入框

UICollectionView拖拽移动单元以及本地保存(上传者:dengqi) UICollectionView拖拽移动单元以及本地保存,可以保存你上次移动的位置。 仿映客直播导航条(上传者:Coolcool) 仿映客直播自定义TabBa…

采集练习(一) php 获得全国的小学(数据来自腾讯朋友网)

注:发现腾讯朋友网已经改版,部分参数需要自己获得修改 !!! 年前有个需求获得某省的小学数据,分析了下朋友网的小学学校发现可以获得相关数据。 如获得 湖南省郴州市宜章县的全部小学 发现网页请求的地址…

android 模板方法模式,安卓设计模式(七)模板方法模式

模板方法模式用于固定相关操作的执行流程,将具体实现延迟到子类中该系列其他文章:定义: 定义一个操作中算法的框架,而降一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤.使用场景:代码重构时,模板方法是经常被用到的,将固定部分提取到父…

solr单机版的搭建

一、solr单机版的搭建 1.运行环境 solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(ja),本教va写的Servlet容器程使用Tocmat作为Servlet容器,环境如下: Solr…

android5.0后新特性修改标题头,Android5.0中Material Design的新特性

Material Design简介Material Design是谷歌新的设计语言,谷歌希望寄由此来统一各种平台上的用户体验,Material Design的特点是干净的排版和简单的布局,以此来突出内容。Material Design对排版、材质、配色、光效、间距、文字大小、交互方式、…

MFCard:易用的信用卡支付集成类库

原文链接:https://github.com/MobileFirstInc/MFCardMFCard:易用的信用卡支付集成类库。# 为开源点赞# —— 由 SwiftLanguage 分享MFCard is an awesome looking Credit Card input & validation control. Written in Swift 3. Demo Usage First St…

Castle ActiveRecord学习(四)延迟加载、分页查询、where条件

一、延迟加载 //用户发布的主题,一对多;Table:外键表;ColumnKey:外键;Lazy:延迟加载;Cascade:级联操作(级联删除)[HasMany(typeof(ThemeInfo), Ta…

系统吞吐量(TPS)、用户并发量、性能测试概念和公式(转载)

原文地址:http://www.ha97.com/5095.html PS:下面是性能测试的主要概念和计算公式,记录下: 一.系统吞度量要素: 一个系统的吞度量(承压能力)与request对CPU的消耗、外部接口、IO等等…

android layout后还原位置,Android图片框架photoview如何记住所有状态并还原,包括缩放度,缩放后的移动的距离等等...

Android图片框架photoview如何记住状态并还原&#xff0c;包括缩放度&#xff0c;缩放后的移动的距离等等,尝试了好多方法都没有作用。private void generateImages() {for (int i 0; i < imagesEntities.size(); i) {// PhotoViewAttacher attacher;final ImagesEntity en…

Shiro安全登录框架

环境准备 本文使用Maven构建&#xff0c;因此需要一点Maven知识。首先准备环境依赖&#xff1a; Java代码 <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <…

iOS自动签名打包(xcodebuild)----常用

iOS自动打包主要用xcodebuild命令, 在终端输入xcodebuild --help可以查看xcodebuild的参数。 xcodebuild具体语法&#xff1a; 无workspace的工程 xcodebuild [-project name.xcodeproj] [[-target targetname] … | -alltargets] [-configuration configurationname] [-sdk [s…

设计模式解析(五)——几种设计模式之Facade和Adapter

由于个人时间原因&#xff0c;无法详细描述这些模式&#xff0c;暂且记录下来以后慢慢补充详细。 Facade模式 Facade模式&#xff1a;关键特征 意图希望简化原有系统的使用方式。需要定义自己的接口。问题只需使用某个复杂系统的子集&#xff0c;或者&#xff0c;需要以一种特殊…

android level list,Android Drawable (levle List selector layer List)

8种机械键盘轴体对比本人程序员&#xff0c;要买一个写代码的键盘&#xff0c;请问红轴和茶轴怎么选&#xff1f;管理大量备选可绘制对象的可绘制对象&#xff0c;每个可绘制对象都分配有最大的备选数量。使用 setLevel() 设置可绘制对象的级别值会加载级别列表中 android:maxL…

人脸检测流程及正负样本下载

源地址&#xff1a;http://www.thinkface.cn/thread-146-1-4.html 人脸检测做训练当然可以用OpenCV训练好的xml&#xff0c;但是岂止于此。我们也要动手做&#xff01;~首先是样本的选取。样本的选取很重要&#xff0c;找了很久才发现几个靠谱的。人脸样本&#xff1a;http://w…

源码推荐:仿写映客直播 ,快速切换主题 ,星星评分控件,表格样式,可以横向移动的表格, 仿微信键盘-

仿写映客直播&#xff08;上传者&#xff1a;五仁月饼&#xff09; 工作之余写的,基于IJKPlayer播放&#xff0c;对内存做了处理。目前已完成直播列表和直播间的搭建&#xff0c;后续还会慢慢完善。 项目地址 publishImageAndVideoAnsRecord&#xff08;上传者&#xff1a;zlj5…

希尔排序——算法系列

希尔排序&#xff1a; 插入排序的升级版&#xff0c;主要采用了分组的策略&#xff0c;采用逐渐减小步长来控制分组的大小&#xff0c;各组内采用插入排序&#xff0c;当步长减小为1的时候&#xff0c;大部分数据都已经有序&#xff0c;所以较插入排序优化了许多。 代码&#x…

android 请求方式有哪些,Android中的几种网络请求方式详解

Android应用经常会和服务器端交互&#xff0c;这就需要手机客户端发送网络请求&#xff0c;下面整理四种常用网络请求方式。java.net包中的HttpURLConnection类Get方式&#xff1a;// Get方式请求public static void requestByGet() throws Exception {String path "http…

Sqlserver的触发器的简单使用

1&#xff0c;触发器有两种 &#xff08;1&#xff09;After触发器&#xff08;之后触发&#xff09; 触发器有个好处&#xff1a;就是你之前有过什么操作他会将你的操作的数据信息完整的保存下来&#xff0c;比如你删过什么信息&#xff0c;如果用触发器&#xff0c;那么删除后…

网络协议OSI、TCP/IP协议、Socket套接字和第三方AsyncSock的使用等解析

一、网络协议定义 1.OSI参考模型:全称(Open System Interconnection), 开放式系统互联参考模型。是一个逻辑上的定义&#xff0c;一个规范&#xff0c;它把网络协议从逻辑上分为七层&#xff0c;只要目的是为解决异种网络互连时所遇到的兼容性问题&#xff0c;其最主要的功能是…

Win8之快速关机

还在纠结如何关机吗&#xff1f;现在教你几招 1、 AltF4快捷键&#xff0c;Windows桌面下按AltF4即可弹出关机菜单&#xff08;保证无任何程序处于被选中状态&#xff0c;可以点击任务栏最右侧 来回到桌面&#xff0c;这时就没问题了&#xff09; 现在怎么关机就不用教了吧。 2…

多键开关 android8.0,手机桌面多键开关(SwitchPro Widget )

7键开关SwitchPro Widget 是款主屏幕窗口小部件工具&#xff0c;可用于开启/关闭多种系统功能&#xff0c;支持多种自定义设置&#xff0c;比原生的电量控制开关好用很多。7键开关SwitchPro Widget并非只有7个按键开关&#xff0c;而是有很多的意思&#xff0c;最多可以设置十几…