美文网首页
iOS - 多线程GCD简介

iOS - 多线程GCD简介

作者: ShIwEn9 | 来源:发表于2019-08-07 10:00 被阅读0次

    GCD因为其强大的处理线程和多核能力,所以在开发使用多线程的时候使用是非常频繁。

    一、GCD简介

    1. 全称:Grand Central Dispatch
    2. 纯C语言编写
    3. 可以自动管理更多的CPU内核
    4. 可以自动管理线程的生命周期(创建、调度、销毁)
    5. 通过线程池来管理线程,将不用的线程放到线程池中,等待调度使用。

    二、GCD中的队列和执行任务的方式

    1. GCD中队列大致可以分为 :
    • 串行队列:Serial Dispatch Queue,每次只有一个任务执行,并且任务是一个接着一个的去执行的。


      串行队列
    • 并发队列(并行队列):Concurrent Dispatch Queue,可以让任务同时去执行,前提条件是在 异步 的情况下

      并发队列
    • 两种队列同时满足先进先出的原则,两者的主要区别是:执行顺序不同,以及开启线程数不同。

    • 一些特殊的队列

      • 主队列:dispatch_get_main_queue()
        特殊的串行队列,但又不同于串行队列。
        以先进先出的形式调度任务,如果主线程上面有任务正在执行,那么会等待主线程任务执行完再去执行任务。
      • 全局队列:dispatch_get_global_queue()
        全局队列的本质就是并发队列,全局队列一直都存在,所以用 ‘get_’
        • 全局队列的两个参数:
    * @param identifier
     * A quality of service class defined in qos_class_t or a priority defined in
     * dispatch_queue_priority_t.
     *
     * It is recommended to use quality of service class values to identify the
     * well-known global concurrent queues:
     *  - QOS_CLASS_USER_INTERACTIVE
     *  - QOS_CLASS_USER_INITIATED
     *  - QOS_CLASS_DEFAULT
     *  - QOS_CLASS_UTILITY
     *  - QOS_CLASS_BACKGROUND
     *
     * The global concurrent queues may still be identified by their priority,
     * which map to the following QOS classes:
     *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
     *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
     *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
     *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
     *
     * @param flags
     * Reserved for future use. Passing any value other than zero may result in
     * a NULL return value.
    

    上面是API中的说明:
    简单来说:第一个参数是服务质量(A quality of service class),也就是 优先级 QOS开头的是被推荐使用的(ios7之后),可以看到和下面的DISPATCH_开头的相对应,优先级从高到低。其中QOS_CLASS_USER_INTERACTIVE的优先级最高。第二个参数是保存起来给将来使用(目前为止都还没有用。。。),通常传个0.

    1. 执行任务方式:
      执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
    • 同步执行(sync)

      • 只会在当前的线程去执行任务,不具备开启新线程的能力。
      • 线程会一个接着一个的去执行。等前一个任务执行完之后,才回去执行后一个任务。
      • 当在主线程里,与主队列同时使用的时候,会出现死锁的情况(主队列等在主线程中的任务执行完毕,主线程等待串行队列中的任务执行完毕,出现相互等待的情况
    • 异步执行(async):

      • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
      • 可以在新的线程中执行任务,具备开启新线程的能力。

    三、GCD的使用

    1. 使用步骤
      GCD使用起来非常的方便:先创建队列,然后将任务追加到等待的队列中,然后系统就自动根据任务的类型执行任务。
    2. 创建GCD多线程
    // 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);
        
    // 创建异步任务
    dispatch_async(queue, ^{
         NSLog(@"求工作");
    });
    

    获取主队列

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    

    获取全局队列

    dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
    

    四. 任务和队列的搭配
    虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:

    同步执行 + 并发队列
    异步执行 + 并发队列
    同步执行 + 串行队列
    异步执行 + 串行队列
    

    实际上,刚才还说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是主队列因为有点特殊,所以我们就又多了四种种组合方式。这样就有八种不同的组合方式了。

    同步执行 + 主队列
    异步执行 + 主队列
    同步执行 + 全局并发队列
    异步执行 + 全局并发队列
    
    1. 串行队列 + 异步执行 = 开启一条线程,串行执行
    // 创建队列
    dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);
        
        // 创建任务
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 并发队列 + 异步执行 = 开启多条线程,同步执行,执行顺序不可控
    dispatch_queue_t queue2 = dispatch_queue_create("qwer", DISPATCH_QUEUE_CONCURRENT);
        // 创建任务
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue2, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 主队列 + 异步执行 = 不开启线程,串行执行,在主线程上面执行
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
        for (int i = 0; i < 10; i++) {
            dispatch_async(mainQueue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 全局并发队列 + 异步执行 = 开启多条线程,同步执行,执行顺序不可控
    dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_async(gloadQueue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 串行队列 + 同步执行 = 不开启线程,任务在主线程上面执行
    // 创建队列
     dispatch_queue_t queue = dispatch_queue_create("qwer", DISPATCH_QUEUE_SERIAL);    
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 并行队列 + 同步执行 = 不开启线程,任务在主线程上面执行
    dispatch_queue_t queue2 = dispatch_queue_create("qwer", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue2, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    1. 主线程 + 同步执行 + 在主线程 = 相互等待,死锁
      主线程 + 同步执行 + 非主线程 = 不开启线程,任务在主线程上面执行
    // 在主线程下
    // 主队列的特点:等待主线程中的代码执行完再去执行主队列中的任务
    // 同步执行又是在主线程执行的,并且会等着第一个任务执行完毕
    // 出现相互等待
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    for (int i = 0; i < 10; i++) {
            dispatch_sync(mainQueue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    
    // 非主线程
    
    1. 全局并发队列 + 同步执行 = 不开启线程,任务在主线程上面执行
    dispatch_queue_t gloadQueue = dispatch_get_global_queue(0, 0);
        for (int i = 0; i < 10; i++) {
            dispatch_sync(gloadQueue, ^{
                NSLog(@"%d--%@",i,[NSThread currentThread]);
            });
        }
    

    通过上面的实际操作可以得出以下的结论:


    同步执行任务的时候是不会开启新的线程。异步在主线程的时候不会开启新线程。

    例子:在网络下载应用:先输入密码--》开始下载 --> 打开应用

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"先输入密码 == %@",[NSThread currentThread]);
            });
    
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"开始下载 == %@",[NSThread currentThread]);
            });
    
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"打开应用 == %@",[NSThread currentThread]);
            });
        });
    

    四、GCD 栅栏(阻塞)方法:dispatch_barrier_async
    当需要异步执行两组操作,并且第一组执行完毕之后才去执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏
    dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。

    栅栏(阻塞)操作
    /**
     * 栅栏方法 dispatch_barrier_async
     */
    - (void)barrier {
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        // 通过栅栏 等上面的线程执行完毕之后再去执行栅栏里面的方法
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务4
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    }
    

    在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

    五、GCD 延迟:dispatch_after
    顾名思义:方法在多少秒之后再执行;
    需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

    /**
         dispatch_time_t when:延迟多长时间执行 精确到纳秒级
         dispatch_queue_t queue:队列
         dispatch_block_t block:任务
         */
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    

    六、GCD一次性执行:dispatc_once()

    • 一次性执行在当前的线程执行:如果在子线程就在子线程执行,在主线程就在主线程执行。
    • 通过判断onceToken的值来实现的,默认onceToken为0,可以执行代码块中的方法,当onceToken不为0的时候,不去执行代码块中的方法。
    • 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。
    • 使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。
    static dispatch_once_t onceToken;
        NSLog(@"%zd",onceToken);
        dispatch_once(&onceToken, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        
        NSLog(@"%zd",onceToken);
    
    // onceToken先后打印 0 和 -1 
    // 
    

    七、队列组:dispatch_group
    有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

    -(void)group{
    
        //创建队列组
    
        dispatch_group_t group = dispatch_group_create() ;
    
        //获得并发队列
    
        dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT) ;
    
        dispatch_queue_t queue2 = dispatch_queue_create("Test2", DISPATCH_QUEUE_CONCURRENT) ;
    
       //封装任务
    
       //代码实现的步骤 01.封装任务。 02.把任务添加到队列中    03 . 监听任务的执行情况(知道任务什么时候执行)
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"0-------%@",[NSThread currentThread]) ;
    
        });
    
        //代码实现的步骤 01.封装任务。 02.把任务添加到队列中
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"1-------%@",[NSThread currentThread]) ;
    
        });
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"2-------%@",[NSThread currentThread]) ;
    
        });
    
        dispatch_group_async(group, queue2,  ^{
    
            NSLog(@"3-------%@",[NSThread currentThread]) ;
    
        });
    
        // 拦截通知,当所有的任务都执行完毕后,执行+++++操作
    
        //这里的queue只是决定block中的任务在那个线程中执行
    
        //dispatch_group_notify。 内部是异步执行
    
        dispatch_group_notify(group, queue, ^{
    
            NSLog(@"++++++%@" ,[NSThread currentThread]) ;
    
        });
    
    }
    
    //    队列组
    
    -(void)group2{
    
        //创建队列组
    
        dispatch_group_t group = dispatch_group_create() ;
    
        //获得并发队列
    
        dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT) ;
    
        //封装任务
    
        //代码实现的步骤 01.封装任务。 02.把任务添加到队列中    03 . 监听任务的执行情况(知道任务什么时候执行)
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"1-------%@",[NSThread currentThread]) ;
    
        });
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"2-------%@",[NSThread currentThread]) ;
    
        });
    
        dispatch_group_async(group, queue, ^{
    
            NSLog(@"3-------%@",[NSThread currentThread]) ;
    
        });
    
        dispatch_group_notify(group, queue, ^{
    
            NSLog(@"++++++%@" ,[NSThread currentThread]) ;
    
        });
    
    }
    
    //队列组的实例应用
    
    -(void)group3{
    
        dispatch_group_t group= dispatch_group_create();
    
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0) ;
    
        //下载图片一
    
        dispatch_group_async(group, queue, ^{
    
            NSURL *url = [NSURLURLWithString:@"[http://e.hiphotos.baidu.com/image/pic/item/adaf2edda3cc7cd9019ce04e3501213fb90e91f0.jpg](http://e.hiphotos.baidu.com/image/pic/item/adaf2edda3cc7cd9019ce04e3501213fb90e91f0.jpg)"] ;
    
            NSData *data = [ NSData dataWithContentsOfURL:url] ;
    
            self.image = [UIImage imageWithData: data] ;
    
            NSLog(@"download-----%@",[NSThread currentThread]) ;
    
        });
    
        //下载图片二
    
        dispatch_group_async(group, queue, ^{
    
            NSURL *url = [NSURLURLWithString:@"[http://h.hiphotos.baidu.com/image/pic/item/50da81cb39dbb6fd35d8b8b30524ab18962b37a7.jpg](http://h.hiphotos.baidu.com/image/pic/item/50da81cb39dbb6fd35d8b8b30524ab18962b37a7.jpg)"] ;
    
            NSData *data = [ NSData dataWithContentsOfURL:url] ;
    
           self.image2 = [UIImage imageWithData: data] ;
    
            NSLog(@"download2-----%@",[NSThread currentThread]) ;
    
        });
    
        //拦截通知,合成图片
    
        dispatch_group_notify(group, queue, ^{
    
            //开启上下文
    
            UIGraphicsBeginImageContext(CGSizeMake(300, 300)) ;
    
            //画图1、2
    
            [self.image drawInRect: CGRectMake(0, 0, 300, 150)] ;
    
            [self.image2 drawInRect:CGRectMake(150, 0, 300, 150)];
    
            NSLog(@"Combie -----%@",[NSThread currentThread]) ;
    
            //根据上下文得到一个图片
    
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext() ;
    
            //关闭上下文
    
            UIGraphicsEndPDFContext() ;
    
            //06 显示图片
    
            dispatch_async(dispatch_get_main_queue(), ^{
    
                self.imageview.image = image;
    
                NSLog(@"UI-------%@",[NSThread currentThread]) ;
    
            });
    
        });
    
    }
    

    参考文章:iOS 多线程:『GCD』详尽总结
    求职广告:因上一家公司资金链,全员被迫离职,现在需要一份iOS开发的工作,杭州南京合肥的都可以。对我感兴趣的可以私聊我 0.0。谢谢~~~

    再次感谢!

    相关文章

      网友评论

          本文标题:iOS - 多线程GCD简介

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