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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

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