对GCD的一些理解和实践

GCD

  GCD,全程Grand Central Dispatch,是苹果为了多核并行提出的解决方案。它是使用C语言实现,但是由于用了block来处理回调,所以使用起来十分方便。并且GCD会自动管理线程的生命周期,不需要我们去管理。

任务和队列

  GCD中有两个重要的概念,任务和队列。

  1、任务,就是我们想要处理的事情,任务可以分为同步执行和异步执行:

  同步(sync):使用dispatch_sync(dispatch_queue_t queue, dispatch_block_t block) 创建,同步执行任务时会阻塞当前线程等待block执行完毕返回。然后当前线程才会继续执行下去。

  异步(async):使用dispatch_async(dispatch_queue_t queue, dispatch_block_t block)创建,异步任务不会阻塞当前线程,任务创建后立即返回线程往下执行。

  2、队列,存放任务,并将任务由先入先出地派发出去。分为串行队列和并行队列:

  串行队列(Serial queue):队列中的任务根据创建顺序,先入先出地执行,等待上一个任务执行完毕后,才会执行下一个任务,有严格的执行先后顺序。

  并行队列(Concurrent queue):队列会根据先后顺序,将任务派发出去,并行执行。所有的任务几乎都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。有一个表,可以大体说明任务和队列之间的配合使用:

  串行队列 并行队列
同步执行任务 当前线程,一个一个执行 当前线程,一个一个执行
异步执行任务 另开线程,一个一个执行 开很多线程,一起执行

  下面是任务和队列的使用演示:

  

/**
     *  串行队列中的任务会等待正在执行的任务执行结束,排队执行
     */
    dispatch_queue_t serial_queue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    //主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    /**
     *  并行,不等待正在执行的任务的处理结果,可以并发执行多个任务
     */
    dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

    //全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //在串行队列中创建几个同步任务,在主线程顺序执行
    dispatch_sync(serial_queue, ^{
        NSLog(@"task a ,thread:%@",[NSThread currentThread]);
    });
    dispatch_sync(serial_queue, ^{
        NSLog(@"task b ,thread:%@",[NSThread currentThread]);
    });
    dispatch_sync(serial_queue, ^{
        NSLog(@"task c ,thread:%@",[NSThread currentThread]);
    });

    //在串行队列中创建几个异步任务,在主线程顺序执行
    dispatch_sync(serial_queue, ^{
        NSLog(@"task aa ,thread:%@",[NSThread currentThread]);
    });
    dispatch_sync(serial_queue, ^{
        NSLog(@"task bb ,thread:%@",[NSThread currentThread]);
    });
    dispatch_sync(serial_queue, ^{
        NSLog(@"task cc ,thread:%@",[NSThread currentThread]);
    });

    //在串行队列中创建几个异步任务,另开1个线程顺序执行
    dispatch_async(serial_queue, ^{
        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);
    });
    dispatch_async(serial_queue, ^{
        NSLog(@"task 1 ,thread:%@",[NSThread currentThread]);
    });

    //在并队列中创建几个异步任务,另开多个线程同时执行
    dispatch_async(concurrent_queue, ^{
        NSLog(@"task 11 ,thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"task 22 ,thread:%@",[NSThread currentThread]);
    });
    dispatch_async(concurrent_queue, ^{
        NSLog(@"task 33 ,thread:%@",[NSThread currentThread]);
    });

任务组和栅栏

  有时候当我们想要为多个任务添加依赖关系的时候,就可以使用任务组dispatch_group和dispatch_barrier。

  任务组是将若干个任务放在一个group 中,这些任务可以在同一队列也可以在不同队列,然后用dispatch_group_notify()和dispatch_group_wait()对任务组中任务的完成进行处理。

1、dispatch_group_notify()中的任务会在group中多有任务执行完毕后执行

dispatch_group_t group = dispatch_group_create();

    /**
     *  group中所有任务任务执行完毕后,执行dispatch_group_notify中的任务
     */
    dispatch_group_async(group, serial_queue, ^{
        sleep(2);
        NSLog(@"serial_queue1");
    });
    dispatch_group_async(group, serial_queue, ^{
        sleep(2);
        NSLog(@"serial_queue2");
    });
    dispatch_group_async(group, concurrent_queue, ^{
        sleep(2);
        NSLog(@"concurrent_queue1");
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"return main queue");
    });

2、dispatch_group_wait()中可传入一个给定时间,如果在等待时间结束前group所有任务执行完毕则返回0,否则返回非0,这个函数是一个同步任务

    /**
     *  dispatch_group_wait给定一个时间,如果在等待时间结束前group所有任务执行完毕则返回0,否则返回非0,这个函数是一个同步任务
     */
    dispatch_group_async(group, serial_queue, ^{
        sleep(3);
        NSLog(@"serial_queue1");
    });
    dispatch_group_async(group, serial_queue, ^{
        sleep(2);
        NSLog(@"serial_queue2");
    });
    dispatch_group_async(group, concurrent_queue, ^{
        sleep(3);
        NSLog(@"concurrent_queue1");
    });

    long i = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 6));
    NSLog(@"-- %ld --",i);
    dispatch_group_async(group, concurrent_queue, ^{
        NSLog(@"finish all");
    });

3、可以使用dispatch_group_enter()和dispatch_group_leave()来添加任务组的任务,两者必须要成对出现,两个函数之间的代码便是要加入任务住的任务

/**
     *  使用dispatch_group_enter和dispatch_group_leave添加组任务,两者必须要成对出现
     */
    dispatch_group_enter(group);
    sleep(2);
    NSLog(@"1");
    dispatch_group_leave(group);

    dispatch_group_enter(group);
    dispatch_async(concurrent_queue, ^{
        sleep(3);
        NSLog(@"2");
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    sleep(2);
    NSLog(@"3");
    dispatch_group_leave(group);

    long i = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 6));
    NSLog(@"-- %ld --",i);
    dispatch_group_async(group, concurrent_queue, ^{
        NSLog(@"finish all");
    });

  栅栏是将一个队列中的任务分割成两部分,在栅栏任务之前添加的任务全部执行完毕后,单独执行栅栏任务,执行完毕后,再继续执行后面的任务。栅栏必须单独执行,不能与其他任务并发执行,因此,栅栏只对并发队列有意义。栅栏只有等待当前队列所有并发任务都执行完毕后,才会单独执行,待其执行完毕,再按照正常的方式继续向下执行。

dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(concurrent_queue, ^{
        sleep(2);
        NSLog(@"1");
    });
    dispatch_async(concurrent_queue, ^{
        sleep(2);
        NSLog(@"2");
    });
    dispatch_async(concurrent_queue, ^{
        sleep(2);
        NSLog(@"3");
    });

    dispatch_barrier_async(concurrent_queue, ^{
        sleep(2);
        NSLog(@"barrier");
    });
    dispatch_async(concurrent_queue, ^{
        sleep(2);
        NSLog(@"finish1");
    });

    dispatch_async(concurrent_queue, ^{
        NSLog(@"finish2");
    });

重复执行dispatchApply 和 单次执行dispatch_once

  dispatch_apply()是将一个任务提交到队列中重复执行,并行或者串行由队列决定,dispatch_apply会阻塞当前线程,等到所有任务完成后返回

dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(5, concurrent_queue, ^(size_t index) {
        sleep(1);
        NSLog(@"index:%zu",index);
    });
    NSLog(@"finish");

  dispatch_once()确保block内代码在整个应用运行期间只执行一次

//确保block内代码在整个应用运行期间只执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"just run once in application");
    });

线程同步、信号量和线程死锁  

  多线程处理经常需要考虑到资源抢占的问题,比如经典的购票问题,写数据库等问题。处理问题的办法有很多,下面介绍最简单的两种做法:加上同步锁,或者用信号量来解决。

  同步锁,每当有线程访问锁里的资源时,会将此部分锁住,拒绝其他线程访问。直到占用的线程推出后才解锁,允许其他资源访问。

//同步锁,对block内的代码加锁,同一时间内只允许一个线程访问
    @synchronized(self) {
        NSLog(@"lock");
    };

  信号量dispatch_semaphore,一开始设置信号的总量,然后用dispatch_semaphore_wait()和dispatch_semaphore_signal()来管理信号量,达到控制线程访问的目的

//信号量dispatch_semaphore
    dispatch_group_t group = dispatch_group_create();

    //设置总信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    for (int i = 0; i<100; i++) {
        //设置等待信号,如果此时信号量大于0,那么信号量减一并继续往下执行
        //如果此时信号量小于0,会一直等待,直到超时
        //如果超时返回非零,成功执行返回0
        dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*50));

        dispatch_group_async(group, concurrent_queue, ^{
            sleep(1);
            NSLog(@"%d",i);
            //发送信号,让信号量加一
            dispatch_semaphore_signal(semaphore);
        });
    }

    dispatch_group_notify(group, concurrent_queue, ^{
        NSLog(@"finish");
    });

  

  GCD尽管使用起来非常方便,但是如果使用不当也活造成一些麻烦,下面列举几个会造成线程死锁的场合:

//在并行队列中,在当前队列调用dispatch_sync,并传入当前队列执行,并不会造成deadlock。dispatch_sync会阻塞当前线程,但是由于队列是并行执行,所以block中的任务会马上执行后返回。
- (void)syncAndConcurrentQueue {
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{

        NSLog(@"Jump to concurrent.queue! ,thread:%@",[NSThread currentThread]);

        dispatch_sync(queue, ^{
            sleep(3);
            NSLog(@"success6 ,thread:%@",[NSThread currentThread]);
        });
         NSLog(@"return");
    });
}

//在串行队列中,在当前队列调用dispatch_sync,并传入当前队列执行,会造成deadlock。 dispatch_sync会阻塞当前线程,等待block中的任务执行完之后再继续执行,但是由于队列是串行执行,block中的任务放在最后,所以永远没有机会执行,线程死锁
- (void)synAndSerialQueue {

    dispatch_queue_t queue = dispatch_queue_create("serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"Jump to serial.queue!");

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

//任务1会阻塞主线程,直到block中执行完毕返回,任务二在主线程添加了了一个同步任务,阻塞当前线程,知道任务执行完毕返回,而任务2没有机会被执行。造成两条线程死锁。
- (void)recycle {

    dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);

    //任务1
    dispatch_sync(concurrent_queue, ^{

        NSLog(@"jump to concurrent queue");

        //任务2
        dispatch_sync(dispatch_get_main_queue(), ^{

            NSLog(@"return main queue");
        });

    });
}

总结

  以上是使用GCD的一些心得,GCD使用起来尽管十分便利,但是在处理一些场景比如取消任务时候会很麻烦,所以实现简单功能的时候推荐使用GCD,如果功能复杂的话建议使用NSOperation和NSOperationQueue,NSOperationQueue的底层也是由GCD实现的,完全面向对象,所以使用起来更好理解。下次有空讲讲NSOperation和NSOperationQueue。以上代码demo地址:https://github.com/GarenChen/GCDDemo

时间: 2024-10-20 13:47:34

对GCD的一些理解和实践的相关文章

GCD的深入理解

GCD 深入理解(一) 本文由@nixzhu翻译至raywenderlich的<grand-central-dispatch-in-depth-part-1> 虽然 GCD 已经出现过一段时间了,但不是每个人都明了其主要内容.这是可以理解的:并发一直很棘手,而 GCD 是基于 C 的 API ,它们就像一组尖锐的棱角戳进 Objective-C 的平滑世界.我们将分两个部分的教程来深入学习 GCD . 在这两部分的系列中,第一个部分的将解释 GCD 是做什么的,并从许多基本的 GCD 函数中找

GCD的个人理解和应用

GCD的个人理解和应用 特点 >>将GCD封装,使我们从繁琐的方法记忆中解脱出来,能够直接快速的应用. 使用方法 1.将工程中的GCD文件中的9个文件拖入自己的工程中(你自己最好建一个文件夹,用来保存这几个文件,方便管理!). 2.在所要用到GCD的view中,导入GCD.h文件 3.封装的GCD文件参见:(https://github.com/YouXianMing/GCD-Program) 源码 1.对GCD封装的源码,在这里下载(https://github.com/YouXianMin

OWIN的理解和实践(三) –Middleware开发入门

原文:OWIN的理解和实践(三) –Middleware开发入门 上篇我们谈了Host和Server的建立,但Host和Server无法产出任何有实际意义的内容,真正的内容来自于加载于Server的Middleware,本篇我们就着重介绍下Middleware的开发入门. Middleware是什么 如果把HTTP交互理解为一次答题活动,那么Request是问题,Response就是答案,Server是课堂,Middleware就是参与者,注意我这里用的是参与而不是解答,因为我们允许有些Midd

OWIN的理解和实践(二) – Host和Server的开发

原文:OWIN的理解和实践(二) – Host和Server的开发 对于开发人员来说,代码就是最好的文档,如上一篇博文所说,下面我们就会基于Kanata项目的一些具体调用代码,来进一步深入理解OWIN的实现和作用. 今天我们先针对Host和Server来实现一个简单的应用. 我们的开发环境是:  VS2013 Update 3,  .Net Framework 4.5.1 Host开发 如上篇博文提及,Host具有如下特点: 实现一个宿主进程 负责Server的启动和关闭 负责Middlewar

OWIN的理解和实践(一) – 解耦,协作和开放

原文:OWIN的理解和实践(一) – 解耦,协作和开放 概述 OWIN的全称是Open Web Interface For .Net, 是MS在VS2013期间引入的全新的概念, 网上已经有不少的关于它的信息, 这里我就谈下我自己的理解: OWIN是一种规范和标准, 不代表特定技术. MS最新出现的一些新的技术, 比如Kanata, Identity, SignalR, 它们只是基于OWIN的不同实现, 这个在以后章节会进一步讨论. OWIN的核心理念是解耦,协作和开放---这和MS以前的风格大

VirtualBox Host-only理解与实践

1 概念理解 host-only顾名思义,这种技术提供的是主机和虚拟机之间的网络互访,而不是虚拟机访问internet的技术. 在某些特殊的网络调试环境中,要求将真实环境和虚拟环境隔离开(就是说不希望外网环境访问虚拟机,也不希望虚拟机访问外网环境),这时你就可采用host-only模式.在host-only模式中,所有的虚拟系统是可以相互通信的,但虚拟系统和真实的网络是被隔离开的. 提示:在host-only模式下,虚拟系统和宿主机器系统是可以相互通信的,相当于这两台机器通过双绞线互连. 个人认

iOS Grand Central Dispatch(GCD) 的简单理解

引言: GCD的全称是Grand Central Dispatch,是苹果在iOS4.0发布的一套处理并发运算方面的API.其用途是为了提高处理器多核运算的能力. GCD有点像NSOperationQueue,它们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行,但GCD比之NSOpertionQueue更底层更高效. GCD的工作原理: GCD的工作原理是让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务. 一个任务可以是一个函数(

对Google C++编程规范的理解和实践

1. #define保护 所有头文件都应该使用#define 防止头文件被多重包含(multiple  inclusion),命名格式为: <PROJECT>_<PATH>_<FILE>_H_ 为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径.例如,项目foo 中的头文件 foo/src/bar/baz.h按如下方式保护: #ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_B

对于src路径问题,深层理解的实践。且对于输出流write()两个方法的源码阅读。

根据昨天的总结,可深层理解图片中src的路径.所以今天实现了一个想法.就是路径写入的是Controller,然后自动去本地找. 其实就是将电脑的本地图片 显示出来.通过输出流的方式. 代码如下: @RequestMapping(value = "/img/{id}") public void img(@PathVariable(value = "id") String id,HttpServletResponse response) { File file = ne