在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,进行图片的合并处理。

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

阻塞任务(dispatch_barrier):

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

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

  1. /* 创建并发队列 */
  2. dispatch_queue_t concurrentQueue = dispatch_queue_create("test.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
  3. /* 添加两个并发操作A和B,即A和B会并发执行 */
  4. dispatch_async(concurrentQueue, ^(){
  5. NSLog(@"OperationA");
  6. });
  7. dispatch_async(concurrentQueue, ^(){
  8. NSLog(@"OperationB");
  9. });
  10. /* 添加barrier障碍操作,会等待前面的并发操作结束,并暂时阻塞后面的并发操作直到其完成 */
  11. dispatch_barrier_async(concurrentQueue, ^(){
  12. NSLog(@"OperationBarrier!");
  13. });
  14. /* 继续添加并发操作C和D,要等待barrier障碍操作结束才能开始 */
  15. dispatch_async(concurrentQueue, ^(){
  16. NSLog(@"OperationC");
  17. });
  18. dispatch_async(concurrentQueue, ^(){
  19. NSLog(@"OperationD");
  20. });
  1. -- ::02.344 SingleView[:] OperationB
  2. -- ::02.344 SingleView[:] OperationA
  3. -- ::02.345 SingleView[:] OperationBarrier!
  4. -- ::02.345 SingleView[:] OperationD
  5. -- ::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的,实际上这里使用信号量实现了两个任务的同步。

  1. /* 创建一个信号量 */
  2. dispatch_semaphore_t semaphore = dispatch_semaphore_create();
  3.  
  4. /* 任务1 */
  5. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
  6. /* 耗时任务1 */
  7. NSLog(@"任务1开始");
  8. [NSThread sleepForTimeInterval:];
  9. NSLog(@"任务1结束");
  10. /* 任务1结束,发送信号告诉任务2可以开始了 */
  11. dispatch_semaphore_signal(semaphore);
  12. });
  13.  
  14. /* 任务2 */
  15. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
  16. /* 等待任务1结束获得信号量, 无限等待 */
  17. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  18. /* 如果获得信号量则开始任务2 */
  19. NSLog(@"任务2开始");
  20. [NSThread sleepForTimeInterval:];
  21. NSLog(@"任务2结束");
  22. });
  23. [NSThread sleepForTimeInterval:];

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

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

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

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

  1. /* 创建一个信号量并初始化为5 */
  2. dispatch_semaphore_t semaphore = dispatch_semaphore_create();
  3.  
  4. /* 模拟1000个等待执行的任务,通过信号量控制最大并发任务数量为5 */
  5. for (int i = ; i < ; i++) {
  6. /* 任务i */
  7. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
  8. /* 耗时任务1,执行前等待信号使信号量减1 */
  9. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  10. NSLog(@"任务%d开始", i);
  11. [NSThread sleepForTimeInterval:];
  12. NSLog(@"任务%d结束", i);
  13. /* 任务i结束,发送信号释放一个资源 */
  14. dispatch_semaphore_signal(semaphore);
  15. });
  16. }
  17. [NSThread sleepForTimeInterval:];
  18.  
  19. 打印结果为每次开启五个并发任务
  20. -- ::27.409067+ OC[:] 任务1开始
  21. -- ::27.409069+ OC[:] 任务2开始
  22. -- ::27.409103+ OC[:] 任务3开始
  23. -- ::27.409268+ OC[:] 任务4开始
  24. -- ::27.409887+ OC[:] 任务0开始
  25.  
  26. -- ::37.415217+ OC[:] 任务1结束
  27. -- ::37.415370+ OC[:] 任务3结束
  28. -- ::37.415217+ OC[:] 任务4结束
  29. -- ::37.415217+ OC[:] 任务2结束
  30. -- ::37.415442+ OC[:] 任务0结束
  31.  
  32. -- ::37.415544+ OC[:] 任务5开始
  33. -- ::37.415548+ OC[:] 任务6开始
  34. -- ::37.415614+ OC[:] 任务9开始
  35. -- ::37.415620+ OC[:] 任务8开始
  36. -- ::37.415594+ OC[:] 任务7开始
  37.  
  38. ... ...

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. js获取子节点和修改input的文本框内容

    js获取子节点和修改input的文本框内容 js获取子节点: $("#"+defaultPVItemId).children().eq(3); //获取某个选择器下的第四个子节点 ...

  2. Ubuntu18.04 更换源

    在虚拟机新建一个Ubuntu18.04.1-live-server-amd64当做服务器 在安装软件时报错: slave@slave:~$ sudo -s[sudo] password for sla ...

  3. do-while语句及for语句(初学者)

    1.do-while语句的一般形式为: do 语句 while(表达式): 这个循环与while循环的不同在于:它先执行循环中的语句,然后再判断这个表达式是否为真,如果为真则继续循环:如果为假,则中止 ...

  4. Pair Programming 2

    学生-社团匹配程序 项目流程: 1. 分析讨论 2. 分工合作 3. 代码规范 4. 编码实现 5. 模块结合 6. 测试修改 7. 数据样例 8. 心得体会 9. GitHub链接 结对队友:陈文举 ...

  5. Python3编写网络爬虫07-基本解析库pyquery的使用

    三.pyquery 简介:同样是一个强大的网页解析工具 它提供了和jQuery类似的语法来解析HTML文档,支持CSS选择器,使用非常方便 安装: pip install pyquery 验证: im ...

  6. Java引用类型转换

    java的引用类型转换分为两种: 向上类型转换,是小类型到大类型的转换 向下类型转换,是大类型到小类型的转换 现存在一个Animal动物类,猫子类和狗子类继承于Animal父类: 1 public c ...

  7. python五十四课——datetime模块

    3.datetime模块:理解:datetime可以认为是time模块的补充/扩展datetime模块中有一些常用类:datetime类:记录了日期和时间数据信息date类:记录了日期数据信息time ...

  8. 【洛谷】【计数原理+Floyed】P1037 产生数

    [题目描述:] 给出一个整数 n \((n<10^{30})\) 和 k 个变换规则\((k≤15)\) . 规则: 一位数可变换成另一个一位数: 规则的右部不能为零. 例如: n=234 .有 ...

  9. php一致性hash算法

    原理部分转自:https://www.jianshu.com/p/e8fb89bb3a61 基本场景 比如你有 N 个 cache 服务器(后面简称 cache ),那么如何将一个对象 object ...

  10. A - I Think I Need a Houseboat HDU - 1065(水题)

    题意:给你一个半圆,半圆以面积每年增加50平方英里的速度扩张.问(x, y)在多少年后被覆盖. 思路:emmm,其实最开始,还是打表的,因为每一年的半圆的半径可以算出来.啊啊啊啊,其实这个方法是可以的 ...