美文网首页
iOS知识总结(三):多线程--GCD的使用

iOS知识总结(三):多线程--GCD的使用

作者: 里克尔梅西 | 来源:发表于2018-01-22 17:35 被阅读5次

多线程的文章在网上已经有很多了,因为项目中多用到的是GCD,所以这篇文章着重说一下GCD的概念和使用,也是对此知识点的一个复习。

进程与线程

在弄清楚多线程前,有必要了解进程和线程的概念以及区别。

进程:

进程是指系统中正在运行的一个程序,进程之间是相互独立的,每个进程都有属于自己的内存空间。也就是说,在iOS系统中中,每一个应用都是一个进程。

线程:

一个进程要想执行任务,必须得有线程,进程中所有的任务都是在线程中进行的,所以每个进程至少要有一条线程,也就是主线程。

线程和进程的比较:

  • 线程是CPU执行任务的最小单位;
  • 进程是CPU分配资源的最小单位;
  • 一个进程中至少有一个线程;
  • 同一个进程内的线程共享进程的资源。

线程的串行:

一个线程中任务的执行是串行的,如果要在一个线程之中执行多个任务,只能一个个地按顺序执行,也就是说在同一时间内一个线程只能执行一个任务。

多线程

一个进程中可以开启多条线程,每条线程可以并行执行不同的任务。

多线程优点

  • 适当提高程序执行效率
  • 适当提高资源利用率

多线程缺点

  • 创建线程需要成本
  • 线程越多,CPU在调度线程上的开销也就越大
  • 线程越多,程序设计也就越复杂:比如线程之间的通信,多线程的数据共享等问题

多线程在iOS中的应用

  1. 主线程:

    • 显示、刷新 UI界面
    • 处理UI事件(比如点击、滚动、拖拽等)
    • 不能把比较耗时的操作放在主线程中,这样会造成卡顿,要将其放在子线程中异步执行。
  2. 子线程
    子线程是异步执行的,不影响主线程,所以将耗时的任务如网络请求、复杂运算等放在子线程中进行,不让其影响UI交互体验。

GCD

全名Grand Central Dispatch,是一套基于C语言的API,它是苹果为多核的并行计算提出的解决方案,它能自动利用更多的内核,可以自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务即可,不需要编写任何线程管理的代码。

任务和队列

任务

任务说白了就是你要执行的那段代码。他们有两种执行方式:同步和异步。

  • 同步(sync):只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕dispatch函数才会返回,然后当前线程才会继续往下运行。
  • 异步(async):可以在新的线程中执行任务,具备开启线程的能力,但不一定会开启新的线程,dispatch函数会立即返回, 然后Block在后台异步执行,即当前线程会直接往下执行,不会阻塞当前线程。

相关代码如下:

#pragma mark - 同步执行
- (void)syncQueue {
    NSLog(@"同步主线程开始");
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.weixin.globalQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"同步线程");
    });
    NSLog(@"同步主线程结束");
}

#pragma mark - 异步执行
- (void)asyncQueue {
    NSLog(@"异步主线程开始");
    dispatch_queue_t queue = dispatch_queue_create("com.qq.globalQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"异步线程");
    });
    NSLog(@"异步主线程结束");
}
结果如下: WX20180119-162214.png
队列

用于存放任务,分为串行队列和并行队列。

  • 串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。

  • 并行队列:可以开启多条线程并行执行任务(但不一定会开启新的线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了

  • 创建队列:

  1. 串行队列
//创建串行队列
dispatch_queue_t firstQueue = dispatch_queue_create("com.weibo", DISPATCH_QUEUE_SERIAL);
  1. 并行队列
//创建并行队列
dispatch_queue_t secondQueue = dispatch_queue_create("com.facebook", DISPATCH_QUEUE_CONCURRENT);
  1. 创建全局默认并发队列
//创建全局默认并发队列
/**
   第一个参数:优先级 也可直接填后面的数字
   #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 // 后台
   第二个参数: 预留参数  0
*/
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1. 获取主队列
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

任务队列组合

  1. 串行队列+同步执行
#pragma mark - 串行队列+同步执行
- (void)queue_taskTest1 {
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"串+同:1 ===== %@",[NSThread currentThread]);
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"串+同:2 ===== %@",[NSThread currentThread]);
    });
    dispatch_sync(self.serialQueue, ^{
        NSLog(@"串+同:3 ===== %@",[NSThread currentThread]);
    });
    NSLog(@"串+同:4 ===== %@",[NSThread currentThread]);
}
结果如下: image.png

全部在当前线程中顺序执行,也就是说同步执行不具备开辟新线程的能力。

  1. 串行队列+异步执行
#pragma mark - 串行队列+异步执行
- (void)queue_taskTest2 {
    dispatch_async(self.serialQueue, ^{
        NSLog(@"串+异:1 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"串+异:2 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.serialQueue, ^{
        NSLog(@"串+异:3 ===== %@",[NSThread currentThread]);
    });
    NSLog(@"串+异:4 ===== %@",[NSThread currentThread]);
}
结果如下: image.png

先打印4,然后再顺序执行子线程中的1、2、3。说明异步执行具备开辟新线程的能力,并且串行队列必须等到一个任务执行完再开始执行下一个,同时异步执行会使内部函数率先返回,不会与正在执行的外部函数发生死锁。

  1. 并行队列+同步执行
#pragma mark - 并行队列+同步执行
- (void)queue_taskTest3 {
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"并+同:1 ===== %@",[NSThread currentThread]);
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"并+同:2 ===== %@",[NSThread currentThread]);
    });
    dispatch_sync(self.concurrentQueue, ^{
        NSLog(@"并+同:3 ===== %@",[NSThread currentThread]);
    });
    NSLog(@"并+同:4 ===== %@",[NSThread currentThread]);
}
结果如下: image.png

同步执行不能开辟新的线程,并行使得block执行完成后dispatch函数才返回,进而继续向下执行。

  1. 并行队列+异步执行
#pragma mark - 并行队列+异步执行
- (void)queue_taskTest4 {
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"并+异:1 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"并+异:2 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"并+异:3 ===== %@",[NSThread currentThread]);
    });
    NSLog(@"并+异:4 ===== %@",[NSThread currentThread]);
}
结果如下: image.png

开辟了多个线程,触发任务的时机是顺序的,但是我们看到完成任务的时间却是随机的,这取决于CPU对于不同线程的调度分配。

  1. 同步执行+主线程 = 线程死锁
#pragma mark - 同步+主队列(线程死锁)
- (void)queue_taskTest5 {
    NSLog(@"同+主队列:1 ==== %@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"同+主队列:2 ==== %@",[NSThread currentThread]);
    });
    NSLog(@"同+主队列:3 ==== %@",[NSThread currentThread]);
}
执行如下: image.png

程序只执行了第一句,然后就崩溃了。原因在于执行完第一句后,同步dispatch_sync就阻塞了当前的主线程,然后把Block中的任务放到主线程中,但这时候这时候是阻塞状态的,所以Block中的任务就没法完成,它没法完成,dispatcch_sync就没法往下进行,一直阻塞着主线程,这就是死锁线程,使得主线程一直处于卡死状态。

GCD其他函数

  1. dispatch_after
#pragma mark - 延迟执行
- (void)delay {
    
    /**
     dispatch_after延迟执行

     @param 第一个参数为延迟执行时间
     @param 第二个参数为执行的队列
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延迟执行---%@",[NSThread currentThread]);
    });
}
  1. dispatch_once
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"执行一次 ==== %@",[NSThread currentThread]);
    });
}

一次性代码主要应用在单例模式中,只执行打印一次,再点击就不会执行了。

  1. dispatch_group_async & dispatch_group_notify
    代码以及说明如下:
#pragma mark - 队列组
- (void)GCDGroup {
    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    //1.开子线程下载图片
    //创建队列(并发)
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //下载图片1
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:data];
        NSLog(@"image1 ==== %@",self.image1);
    });
    
    //下载图片2
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image2 = [UIImage imageWithData:data];
        NSLog(@"image2 ==== %@",self.image2);
    });
    
    //group中所有任务执行完毕,通知该方法执行
    dispatch_group_notify(group, queue, ^{
        //开启图形上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        //画1
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        //画2
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        //根据图形上下文拿到图片
        UIImage *image =  UIGraphicsGetImageFromCurrentImageContext();
        //关闭上下文
        UIGraphicsEndImageContext();
        //回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"%@--刷新UI",[NSThread currentThread]);
        });
    });
}
  1. dispatch_barrier
    将不同的几个异步任务,分成两组,当第一组执行完成后才可以执行第二组,这是我们使用dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

代码如下:

#pragma mark - 栅栏函数
- (void)GCDBarrier {
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:1 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:2 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:3 ===== %@",[NSThread currentThread]);
    });
    dispatch_barrier_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:barrier ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:4 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:5 ===== %@",[NSThread currentThread]);
    });
    dispatch_async(self.concurrentQueue, ^{
        NSLog(@"栅栏函数:6 ===== %@",[NSThread currentThread]);
    });
}

关于GCD的东西先总结这些,好记性不如烂笔头,没事多看看,需要的地方多用用,加油!

相关文章

  • 高级iOS面试题全纪录

    iOS基础: 多线程使用,gcd跟operation区别,怎么取消正在执行的gcd任务 GCD 系列知识总结 NS...

  • iOS多线程:『GCD』详尽总结

    iOS多线程:『GCD』详尽总结 iOS多线程:『GCD』详尽总结

  • iOS多线程之GCD浅析

    本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法,分析GCD的原理,总结GCD的用法和需要注意的地方...

  • GCD

    转载 iOS多线程:『GCD』详尽总结

  • Grand Central Dispatch(GCD)编程基础

    本文介绍iOS 多线程中 GCD 的相关知识以及使用方法。通过本文可以了解到: GCD 简介 GCD 任务和队列 ...

  • 线程

    iOS 多线程:『GCD』详尽总结 NSThread详解 IOS 多线程编程 『NSOperation、NSOpe...

  • iOS 多线程

    参考链接 iOS多线程iOS 多线程:『GCD』详尽总结iOS简单优雅的实现复杂情况下的串行需求(各种锁、GCD ...

  • iOS 多线程学习-思维导图版本

    GCD、NSOperation、NSThread 1.GCD 参考地址:iOS 多线程:『GCD』详尽总结 重要概...

  • iOS多线程 - GCD

    本文用来介绍 iOS 多线程中 GCD的相关知识以及使用方法。通过本文,您将了解到:GCD 简介GCD 任务和队列...

  • 多线程

    参考文章:iOS多线程--彻底学会多线程之『GCD』Swift 3.0 GCD和DispatchQueue 使用解...

网友评论

      本文标题:iOS知识总结(三):多线程--GCD的使用

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