在iOS多线程中我们知道NSOperationQueue操作队列可以直接使用addDependency函数设置操作之间的依赖关系实现线程同步,还可以使用setMaxConcurrentOperationCount函数直接设置最大并发数量。那么在GCD中又是如何实现线程同步和控制最大并发数量的呢?

事实上在之前的问题中我们已经提到了GCD实现线程同步的两种方法了,一种是组队列(dispatch_group_t),另一种是dispatch_barrier_(a)sync,都是等待前面的任务完成后再执行某个任务。除此之外另外一种实现线程同步的方法是信号量机制。

GCD实现线程同步的方法:

组队列(dispatch_group):

举一个例子:用户下载一个图片,图片很大,需要分成很多份进行下载,使用GCD应该如何实现?使用什么队列?

使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行通过dispatch_group_notify添加到主队列中的block,进行图片的合并处理。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片… …
});

阻塞任务(dispatch_barrier):

通过dispatch_barrier_async添加的操作会暂时阻塞当前队列,即等待前面的并发操作都完成后执行该阻塞操作,待其完成后后面的并发操作才可继续。可以将其比喻为一根霸道的独木桥,是并发队列中的一个并发障碍点,或者说中间瓶颈,临时阻塞并独占。注意dispatch_barrier_async只有在并发队列中才能起作用,在串行队列中队列本身就是独木桥,将失去其意义。

可见使用dispatch_barrier_async可以实现类似dispatch_group_t组调度的效果,同时主要的作用是避免数据竞争,高效访问数据。

/* 创建并发队列 */
dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
/* 添加两个并发操作A和B,即A和B会并发执行 */
dispatch_async(concurrentQueue, ^(){
NSLog(@"OperationA");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"OperationB");
});
/* 添加barrier障碍操作,会等待前面的并发操作结束,并暂时阻塞后面的并发操作直到其完成 */
dispatch_barrier_async(concurrentQueue, ^(){
NSLog(@"OperationBarrier!");
});
/* 继续添加并发操作C和D,要等待barrier障碍操作结束才能开始 */
dispatch_async(concurrentQueue, ^(){
NSLog(@"OperationC");
});
dispatch_async(concurrentQueue, ^(){
NSLog(@"OperationD");
});
-- ::02.344 SingleView[:] OperationB
-- ::02.344 SingleView[:] OperationA
-- ::02.345 SingleView[:] OperationBarrier!
-- ::02.345 SingleView[:] OperationD
-- ::02.345 SingleView[:] OperationC

信号量机制(dispatch_semaphore):

信号量机制主要是通过设置有限的资源数量来控制线程的最大并发数量以及阻塞线程实现线程同步等。

GCD中使用信号量需要用到三个函数:

  • dispatch_semaphore_create用来创建一个semaphore信号量并设置初始信号量的值;
  • dispatch_semaphore_signal发送一个信号让信号量增加1(对应PV操作的V操作);
  • dispatch_semaphore_wait等待信号使信号量减1(对应PV操作的P操作);

那么如何通过信号量来实现线程同步呢?下面介绍使用GCD信号量来实现任务间的依赖和最大并发任务数量的控制。

使用信号量实现任务2依赖于任务1,即任务2要等待任务1结束才开始执行:

方法很简单,创建信号量并初始化为0,让任务2执行前等待信号,实现对任务2的阻塞。然后在任务1完成后再发送信号,从而任务2获得信号开始执行。需要注意的是这里任务1和2都是异步提交的,如果没有信号量的阻塞,任务2是不会等待任务1的,实际上这里使用信号量实现了两个任务的同步。

/* 创建一个信号量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(); /* 任务1 */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
/* 耗时任务1 */
NSLog(@"任务1开始");
[NSThread sleepForTimeInterval:];
NSLog(@"任务1结束");
/* 任务1结束,发送信号告诉任务2可以开始了 */
dispatch_semaphore_signal(semaphore);
}); /* 任务2 */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
/* 等待任务1结束获得信号量, 无限等待 */
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/* 如果获得信号量则开始任务2 */
NSLog(@"任务2开始");
[NSThread sleepForTimeInterval:];
NSLog(@"任务2结束");
});
[NSThread sleepForTimeInterval:];

通过打印的时间可以看到任务2是在任务1结束后紧接着执行的:

-- ::37.777156+ OC[:] 任务1开始
-- ::40.782648+ OC[:] 任务1结束
-- ::40.782829+ OC[:] 任务2开始
-- ::43.788198+ OC[:] 任务2结束

通过信号量控制最大并发数量:

通过信号量控制最大并发数量的方法为:创建信号量并初始化信号量为想要控制的最大并发数量,例如想要保证最大并发数为5,则信号量初始化为5。然后在每个新任务执行前进行P操作,等待信号使信号量减1;每个任务结束后进行V操作,发送信号使信号量加1。这样即可保证信号量始终在5以内,当前最多也只有5个以内的任务在并发执行。

/* 创建一个信号量并初始化为5 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(); /* 模拟1000个等待执行的任务,通过信号量控制最大并发任务数量为5 */
for (int i = ; i < ; i++) {
/* 任务i */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
/* 耗时任务1,执行前等待信号使信号量减1 */
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"任务%d开始", i);
[NSThread sleepForTimeInterval:];
NSLog(@"任务%d结束", i);
/* 任务i结束,发送信号释放一个资源 */
dispatch_semaphore_signal(semaphore);
});
}
[NSThread sleepForTimeInterval:]; 打印结果为每次开启五个并发任务
-- ::27.409067+ OC[:] 任务1开始
-- ::27.409069+ OC[:] 任务2开始
-- ::27.409103+ OC[:] 任务3开始
-- ::27.409268+ OC[:] 任务4开始
-- ::27.409887+ OC[:] 任务0开始 -- ::37.415217+ OC[:] 任务1结束
-- ::37.415370+ OC[:] 任务3结束
-- ::37.415217+ OC[:] 任务4结束
-- ::37.415217+ OC[:] 任务2结束
-- ::37.415442+ OC[:] 任务0结束 -- ::37.415544+ OC[:] 任务5开始
-- ::37.415548+ OC[:] 任务6开始
-- ::37.415614+ OC[:] 任务9开始
-- ::37.415620+ OC[:] 任务8开始
-- ::37.415594+ OC[:] 任务7开始 ... ...

GCD实现同步方法的更多相关文章

  1. ios高级开发之多线程(三)GCD技术

    GCD是基于C的API,它是libdispatch的的市场名称.而libdispatch作为Apple公司的一个库,为并发代码在多核硬件(跑IOS或者OS X)上执行提供有力支持. 那么我们为什么要用 ...

  2. 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...

  3. GCD 扫盲篇

    GCD有四个概念:串行队列.并行队列.同步.异步四者. 如下简介: 这里不仅给出了不确定性,而且也给出了确定性.对于初学者而言,有时候因为那些不确定的东西所造成的疑问会像没有闸却在疾驰的汽车一样让人惊 ...

  4. iOS开发之多线程技术——GCD篇

    本篇将从四个方面对iOS开发中GCD的使用进行详尽的讲解: 一.什么是GCD 二.我们为什么要用GCD技术 三.在实际开发中如何使用GCD更好的实现我们的需求 一.Synchronous & ...

  5. iOS多线程之GCD详解

    GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制.也是目前苹果官方推荐的多线程开发方法.iOS三种多线程开发中GCD是抽象层次最高的.当然用起来也是最简单的. ...

  6. ios 多线程小结----- GCD篇

    //3 GCD(充分利用设备的多盒)-------------屏蔽了线程,只能看见任务 队列步骤两步,定制任务,将任务添加到队列.GCD将添加的任务,放到线程中去执行,自动执行,自动释放原则:先进先出 ...

  7. GCD下的几种实现同步的方式

    GCD多线程下,实现线程同步的方式有如下几种: 1.串行队列 2.并行队列 3.分组 4.信号量 实例: 去网上获取一张图片并展示在视图上. 实现这个需求,可以拆分成两个任务,一个是去网上获取图片,一 ...

  8. 多线程&NSObject&NSThread&NSOperation&GCD

    1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 2.NSOperation/NS ...

  9. 多线程 -- GCD

    GCD中有2个核心概念 任务:执行什么操作 队列:用来存放任务 执行任务 同步方法: dispatch_sync dispatch_sync(dispatch_queue_t queue, dispa ...

随机推荐

  1. 关于plist文件的那些事

    今天遇到新生问一个问题,就是用自己定义了一个plist文件,然后可以往里面写东西,但是写过再次运行的时候里面的数据总是最后一次写入的数据.后来就专门研究了一下plist文件. 大家都知道当你创建一个项 ...

  2. 【转载】Linux 内存管理机制

    在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,主要特点是,无论物理内存有多大,Linux 都将其充份利用,将 ...

  3. 从源码的角度分析List与Set的区别

    很多时候我们在讨论List与Set的异同点时都在说: 1.List.Set都实现了Collection接口 2.List是有序的,可以存储重复的元素,允许存入null 3.Set是无序的,不允许存储重 ...

  4. BugFree设置邮箱通知(这里以163邮箱为例)

    公司用bugfree在进行新建Bug指派抄送给同事的时候,总是有人不及时登录BugFree去查看指派给自己的,所以要加一个邮箱通知,这样可以及时通知到被指派的同事. 百度上很多用的是QQ邮箱来实现的, ...

  5. 史上最全脉搏心率传感器PulseSensor资料(电路图+中文说明书+最全源代码)

    准确度说明: 1 输入引脚一定要接在模拟输入口上 ESP-D1  只有一个模拟输入口  A0  0-3.3V  心跳不要接在5v上,否则电压不准  ESP-D1开发板有一个5V和一个3.3v 普通ar ...

  6. 如何使用openscad绘制一个简单的键帽.

    1 新建空项目 2测数据 测量得出数据.这个长方体的长宽高分别是1.6.4.6.8 注意,这三个数据并不是测量得到的数据,而且加了一点公差值(为3D打印做准备) 3画图 写代码 导入模型 为了方便以后 ...

  7. 安卓开发 1配置环境 (JDK+Andriod stiuio)

    配置JDK+Andriod stiuio 所需要用到的JDK和AS 链接:https://pan.baidu.com/s/1smHCD1z密码:rxh2 1 JAVA_SDK   1.1 下载 htt ...

  8. .Net修改网站项目调试时的虚拟目录(未验证)

    有些项目需要在IIS发布的时候,将网站发布到虚拟目录,为了保持调试和发布的路径同一,一般会修改VS调试的虚拟目录 一.Web应用程序 Web应用程序的修改方式非常简单,在解决方案资源管理器->项 ...

  9. linux shell脚本调用java main方法 代码传值

    #!/bin/bash #description: ljdjService export PRG_HOME=/ainmc/work/toptea/dataTransfer PRG_KEYWORD=pr ...

  10. leetcode538. Convert BST to Greater Tree

    https://www.cnblogs.com/grandyang/p/6591526.html 这个题本质上是中序遍历的反向.中序遍历是从小到大,而这个题目是从大到小,然后每个数加上比自己大的所有数 ...