iOS多线程之GCD的详细讲解

由于暂时的工作不忙,所以把自己的的知识写出来给大家分享,后期我依然会写一些技术讲解来提升自己和帮助别人,下面我们就来讲一讲iOS多线程中很重要的GCD。

  • 什么是GCD:
    GCD的全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”;
    纯C语言,提供了非常多强大的函数;
    GCD是苹果公司为多核的并行运算提出的解决方案;
    GCD会自动利用更多的CPU内核(比如双核、四核);
    GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
    程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码;
  • GCD中的两大核心
    任务:执行什么操作
    队列:用来存放任务
  • GCD的使用步骤
    定制任务:确定想做的事情
    将任务添加到队列中:<1>GCD会自动将队列中的任务取出,放到对应的线程中执行。<2>任务的取出遵循队列的FIFO原则,及先进先出,后进后出。
  • GCD中两个用来执行任务的常用函数
    同步的方法执行任务,同步函数
dispatch_sync(dispatch_queue_t  _Nonnull queue, dispatch_block_t block);

异步的方法执行任务,异步函数

dispatch_async(dispatch_queue_t  _Nonnull queue, dispatch_block_t block);

queue---队列:其中并发队列中分为自己创建的队列(dispatch_queue_create)和全局并发队列(dispatch_get_global_queue),串行队列中分为自己创建(dispatch_queue_create)和主队列(和主线程相关联,凡是放在主队列中的任务,必须由主线程执行)
block---任务

  • 同步和异步的区别:
    同步:只能在当前线程中执行任务,不具备开启新线程的能力
    异步:可以在新的线程中执行任务,具备开启新线程的能力(具备但是不一定非要开)
    -GCD的队列可分为两种
    并发队列:<1>可以让多个任务并发(同时)执行(自动开启对个线程同时执行任务,只要第一个任务取出来之后,不在等待执行完毕就可以接着取第二个任务) <2>并发功能只能在异步函数下才有效
    串行队列:让任务一个接着一个执行(一个任务执行完毕后,再执行下一个任务)
  • GCD的几种组合使用
  1. 异步函数+并发队列
/**
 异步函数+并发队列:会开启多条线程,所有的任务并发执行
 //注意:开几条线程并不是由任务数量决定的,是由GCD内部自动决定的
 //输出结果(number=1的才是主线程,其他的编号是由于Xcode添加的)
 2019-02-21 11:16:43.478993+0800 SmallProgram[3127:62594] 3---当前线程<NSThread: 0x600001719700>{number = 3, name = (null)}
 2019-02-21 11:16:43.479036+0800 SmallProgram[3127:62593] 2---当前线程<NSThread: 0x6000017e1840>{number = 8, name = (null)}
 2019-02-21 11:16:43.479042+0800 SmallProgram[3127:62611] 4---当前线程<NSThread: 0x6000017de5c0>{number = 9, name = (null)}
 2019-02-21 11:16:43.479061+0800 SmallProgram[3127:62610] 1---当前线程<NSThread: 0x6000017fbb00>{number = 6, name = (null)}
 */
-(void)asyncConcurrent{
    //01--创建队列
    /**
     参数说明
     第一个参数:C语言的字符串,给队列起个名字,切记不要添加@,因为是C语言的
     第二个参数:类型  DISPATCH_QUEUE_CONCURRENT 并发队列
                   DISPATCH_QUEUE_SERIAL 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_CONCURRENT);
    //02--封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    
}
  1. 异步函数+串行队列
/**
 异步函数+串行队列:会开启一条子线程,所有的任务在该子线程中串行执行
 //输出结果(number=1的才是主线程,其他的编号是由于Xcode添加的)
 2019-02-21 11:21:50.500116+0800 SmallProgram[3205:66377] 1---当前线程<NSThread: 0x600000f30740>{number = 7, name = (null)}
 2019-02-21 11:21:50.500287+0800 SmallProgram[3205:66377] 2---当前线程<NSThread: 0x600000f30740>{number = 7, name = (null)}
 2019-02-21 11:21:50.500399+0800 SmallProgram[3205:66377] 3---当前线程<NSThread: 0x600000f30740>{number = 7, name = (null)}
 2019-02-21 11:21:50.500515+0800 SmallProgram[3205:66377] 4---当前线程<NSThread: 0x600000f30740>{number = 7, name = (null)}
 */
-(void)asyncSerial{
    //01--创建队列
    /**
     参数说明
     第一个参数:C语言的字符串,给队列起个名字,切记不要添加@,因为是C语言的
     第二个参数:类型  DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
    //02--封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    
}
  1. 同步函数+并发队列
/**
 同步函数+并发队列:(同步函数不具备开始线程)不会开启子线程,所有的任务在当前线程中串行执行
 //输出结果(number=1的才是主线程,其他的编号是由于Xcode添加的)
 2019-02-21 11:30:45.546540+0800 SmallProgram[3349:71630] 1---当前线程<NSThread: 0x600003dd6940>{number = 1, name = main}
 2019-02-21 11:30:45.546734+0800 SmallProgram[3349:71630] 2---当前线程<NSThread: 0x600003dd6940>{number = 1, name = main}
 2019-02-21 11:30:45.546859+0800 SmallProgram[3349:71630] 3---当前线程<NSThread: 0x600003dd6940>{number = 1, name = main}
 2019-02-21 11:30:45.546976+0800 SmallProgram[3349:71630] 4---当前线程<NSThread: 0x600003dd6940>{number = 1, name = main}
 */
-(void)syncConcurrent{
    //01--创建队列
    /**
     参数说明
     第一个参数:C语言的字符串,给队列起个名字,切记不要添加@,因为是C语言的
     第二个参数:类型  DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_CONCURRENT);
    //02--封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    
}
  1. 同步函数+串行队列
/**
 同步函数+串行队列:(同步函数不具备开始线程)不会开启子线程,所有的任务在当前线程中串行执行
 //输出结果(number=1的才是主线程,其他的编号是由于Xcode添加的)
 2019-02-21 11:33:36.127713+0800 SmallProgram[3420:73690] 1---当前线程<NSThread: 0x600003c00d80>{number = 1, name = main}
 2019-02-21 11:33:36.127911+0800 SmallProgram[3420:73690] 2---当前线程<NSThread: 0x600003c00d80>{number = 1, name = main}
 2019-02-21 11:33:36.128021+0800 SmallProgram[3420:73690] 3---当前线程<NSThread: 0x600003c00d80>{number = 1, name = main}
 2019-02-21 11:33:36.128148+0800 SmallProgram[3420:73690] 4---当前线程<NSThread: 0x600003c00d80>{number = 1, name = main
 */
-(void)syncSerial{
    //01--创建队列
    /**
     参数说明
     第一个参数:C语言的字符串,给队列起个名字,切记不要添加@,因为是C语言的
     第二个参数:类型  DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.baidu.www", DISPATCH_QUEUE_SERIAL);
    
    //02--封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    
}
  1. 异步函数+主队列
/**
 异步函数+主队列:不会开线程,所有任务都在主线程中串行执行
 //输出结果
 2019-02-21 13:37:50.406381+0800 SmallProgram[5481:145502] 测试1
 2019-02-21 13:37:50.406381+0800 SmallProgram[5481:145502] 测试2
 2019-02-21 13:37:50.406381+0800 SmallProgram[5100:128550] 1---当前线程<NSThread: 0x60000336d7c0>{number = 1, name = main}
 2019-02-21 13:37:50.406613+0800 SmallProgram[5100:128550] 2---当前线程<NSThread: 0x60000336d7c0>{number = 1, name = main}
 2019-02-21 13:37:50.406778+0800 SmallProgram[5100:128550] 3---当前线程<NSThread: 0x60000336d7c0>{number = 1, name = main}
 2019-02-21 13:37:50.406936+0800 SmallProgram[5100:128550] 4---当前线程<NSThread: 0x60000336d7c0>{number = 1, name = main}
 */
-(void)asyncMain{
    //01--创建队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"测试1");
    //02--封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    NSLog(@"测试2");
} 
  1. 同步函数+主队列:
/**
 同步函数+主队列:
 情况一:在主线程调用syncMain
 分析:不会开线程,这段代码会死锁崩溃(队列为主队列,所以只能在主线程中执行,但是当前主线程在执行syncMain方法,被占用着,因为当前函数还未运行完),此时思考下异步+主队列为啥不死锁?这个面试官很爱问的。答案是:因为是异步的,所以程序可以先执行完一个任务在执行另外一个任务。
 解释:当主队列中有任务的时候,主队列就会安排主线程来来执行该任务,但是在调度之前会先检查主线程的状态(是否在忙),如果主线程当前在忙,那么暂停调度,值到主线程空闲为止。
 情况二:在子线程调用syncMain
 分析:因为在走dispatch_sync方法的时候主线程在空闲状态。
 输出结果:
 2019-02-21 13:55:13.366052+0800 SmallProgram[5384:140751] 1---当前线程<NSThread: 0x60000124d000>{number = 1, name = main}
 2019-02-21 13:55:13.366318+0800 SmallProgram[5384:140751] 2---当前线程<NSThread: 0x60000124d000>{number = 1, name = main}
 2019-02-21 13:55:13.366544+0800 SmallProgram[5384:140751] 3---当前线程<NSThread: 0x60000124d000>{number = 1, name = main}
 2019-02-21 13:55:13.366828+0800 SmallProgram[5384:140751] 4---当前线程<NSThread: 0x60000124d000>{number = 1, name = main}
 */
-(void)syncMain{
    //01--创建队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //02--封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3---当前线程%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"4---当前线程%@",[NSThread currentThread]);
    });
    
}
  • GCD的日常几点使用
  1. GCD的一次性代码:
   -(void)once{
    //整个程序运行过程中只会执行一次+本身是线程安全的
    //应用:单例模式
    //内部原理:通过判断onceToken的值来决定是否执行block中的任务,只有在第一次为0,其它都为-1
    static dispatch_once_t onceToken;
    NSLog(@"%zd",onceToken);
    dispatch_once(&onceToken, ^{
        //任务
    });
    
}
  1. GCD的延迟执行:
-(void)delay{
    
    /**
     GCD的延迟执行
     @param when#> 设置时间(CGD中的时间单位为纳秒) description#>
     @param queue#> 队列(决定block块红的任务在哪个线程中执行,如果主队列就在主线程,否则在子线程) description#>
     @param void 执行的任务
     原理:先等2秒,然后在吧任务提交到队列,如果先提交到队列,那么任务不好控制,并且很好队列的资源
     */
    NSLog(@"hahaha");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"hahaha%@",[NSThread currentThread]);
    });
}
  1. GCD的快速迭代:(小应用:将一个文件夹下多张图片剪切到另外一个文件夹下)
//快速迭代(遍历)
-(void)apply{
    //在当前线程中串行执行----0---<NSThread: 0x600003b9d680>{number = 1, name = main}
    for(int i=0;i<10;i++){
        NSLog(@"%d---%@",i,[NSThread currentThread]);
    }
    /**
     //快速迭代--会开启多条子线程和主线程一起并发的执行任务
     iterations#> 遍历的次数
     queue#> 队列 --如果是主队列则死锁,如果是串行队列则跟for循环一样
     size_t 索引
     */
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_apply(10, queue, ^(size_t i) {
        NSLog(@"%zd---%@",i,[NSThread currentThread]);
    });
}
  1. GCD的栅栏函数()
-(void)barrier{
    //栅栏函数
    //需求:有一个新任务打印00000的任务,要求在1 2执行完之后执行,要保证该任务执行完之后才能执行后面的3 4任务。
    //讲解:栅栏前面的任务并发执行,后面的任务也是并发执行,当前面的任务执行完之后执行栅栏函数中的任务,等该任务执行完毕后在执行后面的任务。
    //警告:不能使用全局并发队列(dispatch_get_global_queue(0, 0)),否则不能拦截
    //01获得队列
    //
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    //02封装任务,并且添加到队列
    dispatch_async(queue, ^{
        NSLog(@"01---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"02---%@",[NSThread currentThread]);
    });
    //栅栏函数
    dispatch_barrier_async(queue, ^{
        NSLog(@"00000");
    });
    dispatch_async(queue, ^{
        NSLog(@"03---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"04---%@",[NSThread currentThread]);
    });
    
}
  1. GCD的队列组(控制多条队列中任务都完成在执行新的,可用在下载多张小图片,然后都下载完成后拼接起来)
//GCD的队列组
-(void)group{
    //需求:有5个任务,在多个队列的子线程中并发执行,添加打印00000的任务,必须在所有任务完成后在执行
    //增加需求:拦截多个队列中的任务
    
    //01 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t queue01 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue02 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue01, ^{
        NSLog(@"01---%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue01, ^{
        NSLog(@"02---%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue01, ^{
        NSLog(@"03---%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue02, ^{
        NSLog(@"04---%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue02, ^{
        NSLog(@"05---%@",[NSThread currentThread]);
    });
    //03 拦截通知,当所有的任务都执行完毕然后打印00000
    //注意:通知中的第二个参数能控制执行最后的任务在子线程还是主线程
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"00000--%@",[NSThread currentThread]);
    });
    
}
  • 使用需要注意的地方:鄙人这里测试只是在主线程中来调用方法执行的,例如子线程中或者子线程的子线程的情况并未测试,还需大家去探索,但是我们最主要的是要理解同步、异步、串行、并行的原则,来做到以不变应万变。
  • 总结:
    同步函数:
    +串行队列:不会开线程,所有任务在当前线程串行执行
    +并发队列:不会开线程,所有任务在当前线程串行执行
    +主队列:死锁
    异步函数:
    +串行队列:会开1条线程,所有任务在子线程中串行执行
    +并发队列:会开N条线程,所有任务在子线程中并发执(注意:线程的数量并不等于任务的数量)
    +主队列:不会开线程,所有任务在主线程串行执行

以上内容有什么问题还请私信告知,不喜勿喷谢谢,有什么不明白的也可以讨论。

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