iOS 多线程入门03--GCD

一.什么是GCD?

1.GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式。
2.GCD是基于C语言的线程管理方案,使用者无需过多参与线程的管理,只需要将想要执行的代码,添加到想要添加的调度队列即可。
3.GCD主要用在后台执行较慢任务;延迟执行任务;以及在后台任务中,切换回主线程,更新UI。


二.GCD的优势

GCD是苹果公司为多核的并行运算提出的解决方案。
GCD会自动利用更多的CPU内核(比如双核、四核)。
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。


三.任务和队列

3.1 队列

队列:用于存放要执行的任务。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在 GCD中有两种队列:

  • 串行队列(Serial Dispatch Queue)

一个任务执行完毕后,再执行下一个任务

  • 并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程执行任务)并发功能只有在异步(dispatch_async) 函数下才有效

DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因为宏定义定义的为NULL。
DISPATCH_QUEUE_CONCURRENT:表示并发

3.2 任务

任务:在线程中执行的操作,GCD中就是指Block中的代码。任务有两种方式:同步执行(sync)和异步执行(async)。区别在于是否会创建新的线程。同步和异步的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕。

  • 同步执行:当前任务不完成,不会执行下个任务。
  • 异步执行:当前任务不完成,不会等待,同样可以执行下个任务。

四.GCD的串行队列,并发队列和全局队列

各种队列的执行效果:


image.png

4.1并发队列

1.并发队列,同步任务(不会开启新的线程,并发队列失去了并发的功能。)
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
    
    //2.添加任务到队列中执行
    for (int i=0; i<10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");
总结:不会开启线程,并且会顺序执行。
2.并发队列,异步任务(执行较慢的任务,例如大量计算,网络请求等。)
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
    
//2.添加任务到队列中执行
    for (int i=0; i<10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
    
NSLog(@"come here");
总结:会开启线程,不会顺序执行。

4.2串行队列

1.串行队列,同步任务(在新线程中执行任务,并且等待线程执行完毕再向后执行,几乎不用)
    //1.创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
    
    //2.添加任务到队列中执行
    for (int i=0; i<10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"%@ %d",[NSThread currentThread],i);
        });
    }
2.串行队列,异步任务
dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);

for (int i=0; i<10; i++) {
      NSLog(@"%d-----",i); //主线程,会打印完9之后,才会执行come here
      dispatch_async(queue, ^{
          NSLog(@"%@ %d",[NSThread currentThread],i); //子线程
      });
}
    NSLog(@"come here"); //在主线程,和队列没有任何关系
2021-03-23 23:35:59.401760+0800 多线程Demo[32679:1746588] 0-----
2021-03-23 23:35:59.401965+0800 多线程Demo[32679:1746588] 1-----
2021-03-23 23:35:59.402048+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 0
2021-03-23 23:35:59.402079+0800 多线程Demo[32679:1746588] 2-----
2021-03-23 23:35:59.402199+0800 多线程Demo[32679:1746588] 3-----
2021-03-23 23:35:59.402266+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 1
2021-03-23 23:35:59.402307+0800 多线程Demo[32679:1746588] 4-----
2021-03-23 23:35:59.402397+0800 多线程Demo[32679:1746588] 5-----
2021-03-23 23:35:59.402404+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 2
2021-03-23 23:35:59.402680+0800 多线程Demo[32679:1746588] 6-----
2021-03-23 23:35:59.402908+0800 多线程Demo[32679:1746588] 7-----
2021-03-23 23:35:59.403205+0800 多线程Demo[32679:1746588] 8-----
2021-03-23 23:35:59.403466+0800 多线程Demo[32679:1746588] 9-----
2021-03-23 23:35:59.403737+0800 多线程Demo[32679:1746588] come here
2021-03-23 23:35:59.404267+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 3
2021-03-23 23:35:59.404670+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 4
2021-03-23 23:35:59.405344+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 5
2021-03-23 23:35:59.405665+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 6
2021-03-23 23:35:59.406199+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 7
2021-03-23 23:35:59.407880+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 8
2021-03-23 23:35:59.408041+0800 多线程Demo[32679:1746641] <NSThread: 0x6000013242c0>{number = 7, name = (null)} 9
  • 首先肯定是打印完9之后,才会打印come here,因为都是在主线程,然后打印数字和打印线程是交叉执行的。
总结:会开启新的线程,任务会顺序完成。

4.3主队列

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
  • 总结:不会开启新线程,串行执行任务。
    dispatch_sync(dispatch_get_main_queue(), ^{
          NSLog(@“在同步主线程中执行,慎用,否则会死锁”);
    });
  • 总结:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)

五.GCD的其他用法以及注意事项

5.1异步主线程(用于在后台线程的任务将要完成时,切换到主线程更新UI)(不会开新线程)
 //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
 dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"在异步主线程中执行");
    });
// 执⾏耗时的异步操作...
    dispatch_async( dispatch_get_global_queue(0, 0), ^{ //请求数据
       
        dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,执⾏UI刷新操作
            //对图片或别的操作进行赋值等,回到主线程

        });
    });
5.2全局队列(相当于并发队列)
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
5.3延时执行线程
    //延迟
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"我在五秒后打印");
    });
5.4单例模式(只执行一次)
static GGT_Singleton *singleton = nil;  //在.m中保留一个全局的static的实例


+ (GGT_Singleton *)sharedSingleton {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singleton = [[GGT_Singleton alloc]init];
    });
    
    return singleton;
}
5.5队列组

如果有三个异步操作(例:网络请求ABC),想在三个请求全部执行完返回结果后,再做其他操作。
1、这三个网络请求,执行无序,并发执行。
2、不论返回失败还是成功都算返回结果。
如何处理?

如果满足上面的需求,需要使用GCD里面的dispatch_group_enter和dispatch_group_leave来实现。
dispatch_group_enter:通知 group,下个任务要放入 group 中执行了。
dispatch_group_leave:通知 group,任务成功完成,要移除,与enter()成对出现。
dispatch_group_notify:只要任务组完成才会调用,不完成不会调用。

    dispatch_group_t group =  dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_enter(group);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"1   %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"2   %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
        NSLog(@"结束");
    });
2022-02-11 18:18:51.877309+0800 TestDemo[14721:238852] 1   <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071275+0800 TestDemo[14721:238852] 2   <NSThread: 0x6000026d6d00>{number = 3, name = (null)}
2022-02-11 18:18:53.071532+0800 TestDemo[14721:238763] 结束
5.6快速迭代(使用dispatch_apply函数能进行快速迭代遍历)
 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ //%zu用来输出size_t 类型
        // 执行10次代码,index顺序不确定
        NSLog(@"%zu",index);
    });
5.7 iOS中的多读单写安全方案
//初始化队列
dispatch_queue_t queue = dispatch_queue_create("rw_queue",DISPATCH_QUEUE_CONCURRENT);
    
//读
dispatch_async(queue, ^{
});
    
//写
dispatch_barrier_async(queue, ^{
});


六.小结

  • 同步任务死锁:当前是在主线程,让主队列执行同步任务!
    //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
    //1.队列 --> 已启动主线程,就可以获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    
    //2.同步任务  ==> 死锁
    dispatch_sync(q, ^{
        NSLog(@"能来吗? ");
    });
    NSLog(@"come here");

解决办法:

  void (^task)() = ^{
        NSLog(@"这里%@",[NSThread currentThread]);
        //1.队列 --> 已启动主线程,就可以获取主队列
        dispatch_queue_t q = dispatch_get_main_queue();
        
        //2.同步任务
        dispatch_sync(q, ^{
            NSLog(@"能来吗? %@",[NSThread currentThread]);
        });
        
        NSLog(@"come here");
    };
    
    dispatch_async(dispatch_get_global_queue(0, 0), task);
  • 竞争&同步:两个线程抢夺同一个资源,就会竞争,为了防止竞争,一个线程拥有资源的时候,会对资源加锁,另一个线程就要等待解锁以后再拥有这个资源,这叫同步。

  • 死锁:两个线程互相等待对方释放资源。

  • 主线程&后台线程:主线程也叫前台线程,程序启动的默认线程,操作UI的线程。后台线程,即非主线程,用于不影响主线程的完成一些任务。

  • 同步&异步:同步执行线程,等待新线程执行完以后,再继续执行当前线程,很少用到。异步执行线程,在执行新线程的同时,继续执行当前线程,常用。

iOS 多线程入门01--概念知识
iOS 多线程入门02--NSThread
iOS 多线程入门04--NSOperation

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,616评论 0 4
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 401评论 0 0
  • 原创文章 转载请注明出处, 谢谢! (~ o ~)Y 本文思维导图 GCD是什么 全称是 Grand Centra...
    Jimmy_P阅读 4,719评论 10 67
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,822评论 1 17
  • 之前给大家介绍过一款名叫“硕鼠”的网页视频下载工具(没看过的朋友请点击阅读原文查看),相信大家对如何下载网页的视频...
    缘小异阅读 471评论 0 0