美文网首页
Something About GCD

Something About GCD

作者: Z_Han | 来源:发表于2016-07-31 13:38 被阅读53次
    一、GCD介绍
    1. 简介

    并发处理能够同时处理多个任务。异步设计方法可以充分地发挥多核优势。GCD(Grand Central Dispatch)是一种异步方法,是专为多核CPU设计的并发处理技术。GCD使用纯C语言编写,提供了非常多强大的函数。

    2. GCD的优势
    • GCD是苹果公司为多核并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
    3. 注意
    • GCD是纯 C 语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法
    • GCD中的函数大多数都以 dispatch 开头
    • 将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出
    4. 添加任务到队列
    • 用同步的方式
    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    • 用异步的方式
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    

    说明:把右边的block(任务)提交给左边的queue(队列)进行执行。

    5. 任务的执行
    • 同步:当前线程中执行
    • 异步:另开一条线程执行
    6. serial dispatch queue
    • 使用 dispatch_queue_create 函数创建串行队列
    // 队列名称, 队列属性,一般用NULL即可
    dispatch_queue_t  dispatch_queue_create(const char *label,  dispatch_queue_attr_t attr); 
    示例:
    dispatch_queue_t queue = dispatch_queue_create("wendingding", NULL); // 创建
    dispatch_release(queue); // 非ARC需要释放手动创建的队列
    
    • 使用主队列(跟主线程相关联的队列)
    //主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
    //使用dispatch_get_main_queue()获得主队列
    示例:
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    7. concurrent dispatch queue

    GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建

    //使用dispatch_get_global_queue函数获得全局的并发队列
    //获取默认优先级的全局并发dispatch queue
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    
    说明:全局并发队列的优先级
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
    
    8. 队列执行效果

    同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)

    // 调用前,查看下当前线程  
    NSLog(@"当前调用线程:%@", [NSThread currentThread]);  
    // 创建一个串行queue  
    dispatch_queue_t queue = dispatch_queue_create("cn.itcast.queue", NULL);  
    dispatch_async(queue, ^{  
        NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);  
    });    
    dispatch_sync(queue, ^{  
        NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);  
    }); 
    

    运行结果:

    2016-07-31 09:03:37.348 thread[6491:c07] 当前调用线程:<NSThread: 0x714fa80>{name = (null), num = 1}  
    2016-07-31 09:03:37.349 thread[6491:1e03] 开启了一个异步任务,当前线程:<NSThread: 0x74520a0>{name = (null), num = 3}  
    2016-07-31 09:03:37.350 thread[6491:c07] 开启了一个同步任务,当前线程:<NSThread: 0x714fa80>{name = (null), num = 1}  
    

    添加一个任务到Queue,尽可能地使用dispatch_asyncdispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件

    绝对不要在任务中调用 dispatch_syncdispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做

    二、主队列
    • 主队列是GCD自带的一种特殊的串行队列,放在主队列中得任务,都会放到主线程中执行。如果把任务放到主队列中进行处理,那么不论处理函数是异步的还是同步的都不会开启新的线程。
    • 调用 dispatch_get_main_queue 函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行
    // 异步下载图片  
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
        //执⾏耗时的异步操作... 
        NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];  
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];  
          
        dispatch_async(dispatch_get_main_queue(), ^{  
            // 回到主线程显示图片  
            // 主线程拿到加载的image刷新UI界面。
            self.imageView.image = image;  
        });  
    });  
    

    任务中使用Objective-C对象
    GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。

    三、Dispatch Group的使用

    使用dispatch_group_async 函数将多个任务关联到一个 Dispatch Group 和相应的 queue 中,group 会并发地同时执行这些任务。
    问题:如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
    如果想要快速高效地实现上述需求,可以考虑用队列组

    dispatch_group_t group =  dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
    });
    

    例如:

    // 封装一个方法,传入一个url参数,返回一张网络上下载的图片 
    - (UIImage *)imageWithURLString:(NSString *)urlString {  
        NSURL *url = [NSURL URLWithString:urlString];  
        NSData *data = [NSData dataWithContentsOfURL:url];  
        // 这里并没有自动释放UIImage对象  
        return [[UIImage alloc] initWithData:data];  
    }  
      
    - (void)downloadImages {  
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
        // 异步下载图片  
        dispatch_async(queue, ^{  
            // 创建一个组  
            dispatch_group_t group = dispatch_group_create();  
            __block UIImage *image1 = nil;  
            __block UIImage *image2 = nil;  
              
            // 关联一个任务到group,下载第一张图片
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{    
                NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
                image1 = [self imageWithURLString:url1];  
            });  
            // 关联一个任务到group,下载第二张图片  
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
                NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
                image2 = [self imageWithURLString:url2];  
            });  
              
            // 等待组中的任务执行完毕,回到主线程执行block回调  
            dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
                self.imageView1.image = image1;  
                self.imageView2.image = image2;  
     
               //合并两张图片 
               //注意最后一个参数是浮点数(0.0),不要写成0
              UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0); 
               [image1 drawInRect:CGRectMake(0, 0, 100, 100)]; 
               [image2 drawInRect:CGRectMake(100, 0, 100, 100)]; 
               self.imageView3.image=UIGraphicsGetImageFromCurrentImageContext(); 
               //关闭上下文 
               UIGraphicsEndImageContext(); 
               NSLog(@"图片合并完成---%@",[NSThread currentThread]);            
            });  
        });  
    } 
    

    dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行

    四、GCD其他用法
    1. 用于延时
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
           //延迟执行的方法
    
        });
    

    延迟执行:不需要再写方法,且它还传递了一个队列,我们可以指定并安排其线程。
    如果队列是主队列,那么就在主线程执行,如果队列是并发队列,那么会新开启一个线程,在子线程中执行。

    1. 一次性代码
    //使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
    

    整个程序运行过程中,只会执行一次。

    五、回答常见问题
    1. dispatch_barrier_async的作用是什么?
      在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用barrier来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
      (注意:使用 dispatch_barrier_async,该函数只能搭配自定义并行队列 dispatch_queue_t使用。不能使用:dispatch_get_global_queue,否则 dispatch_barrier_async的作用会和 dispatch_async的作用一模一样。)

    2. 苹果为什么要废弃dispatch_get_current_queue
      dispatch_get_current_queue容易造成死锁

    非常感谢:文顶顶M了个J

    相关文章

      网友评论

          本文标题:Something About GCD

          本文链接:https://www.haomeiwen.com/subject/prwtsttx.html