美文网首页
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知识总结(三):多线程--GCD的使用

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