美文网首页iOS开发常看iOS开发技术
iOS多线程:『GCD』详尽总结

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

作者: ITCharge | 来源:发表于2016-09-03 19:47 被阅读67153次

    感谢大家对这篇文章的喜欢和支持。为了不辜负大家的喜欢,也为了更好的让大家了解 iOS 多线程,以及 GCD 的相关知识,我对这篇文章进行了重新梳理,在原有文章的基础上修改了原文存在的问题,并增加了更多关于 GCD 相关知识和使用方法,希望大家能够喜欢这篇新文章。

    本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲解+总结的文章了。通过本文,您将了解到:
    1. GCD 简介
    2. GCD 任务和队列
    3. GCD 的使用步骤
    4. GCD 的基本使用(6种不同组合区别)
    5. GCD 线程间的通信
    6. GCD 的其他方法(栅栏方法:dispatch_barrier_async、延时执行方法:dispatch_after、一次性代码(只执行一次):dispatch_once、快速迭代方法:dispatch_apply、队列组:dispatch_group、信号量:dispatch_semaphore)

    文中 Demo 我已放在了 Github 上,Demo 链接:传送门

    1. GCD 简介

    什么是 GCD 呢?我们先来看看百度百科的解释简单了解下概念

    引自百度百科
    Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

    为什么要用 GCD 呢?

    因为 GCD 有很多好处啊,具体如下:

    • GCD 可用于多核的并行运算
    • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
    • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

    既然 GCD 有这么多的好处,那么下面我们就来系统的学习一下 GCD 的使用方法。

    2. GCD 任务和队列

    学习 GCD 之前,先来了解 GCD 中两个核心概念:任务队列

    任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

    • 同步执行(sync)
      • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
      • 只能在当前线程中执行任务,不具备开启新线程的能力。
    • 异步执行(async)
      • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
      • 可以在新的线程中执行任务,具备开启新线程的能力。

    举个简单例子:你要打电话给小明和小白。
    同步执行就是,你打电话给小明的时候,不能同时打给小白,等到给小明打完了,才能打给小白(等待任务执行结束)。而且只能用当前的电话(不具备开启新线程的能力)。
    而异步执行就是,你打电话给小明的时候,不等和小明通话结束,还能直接给小白打电话,不用等着和小明通话结束再打(不用等待任务执行结束)。除了当前电话,你还可以使用其他所能使用的电话(具备开启新线程的能力)。

    注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。

    队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:

    队列(Dispatch Queue).png

    在 GCD 中有两种队列:串行队列并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

    • 串行队列(Serial Dispatch Queue)
      • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
    • 并发队列(Concurrent Dispatch Queue)
      • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

    注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

    两者具体区别如下两图所示。

    串行队列.png 并发队列.png

    3. GCD 的使用步骤

    GCD 的使用步骤其实很简单,只有两步。

    1. 创建一个队列(串行队列或并发队列)
    2. 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

    下边来看看队列的创建方法/获取方法,以及任务的创建方法。

    3.1 队列的创建方法/获取方法

    • 可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
    // 串行队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    // 并发队列的创建方法
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    • 对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)
      • 所有放在主队列中的任务,都会放到主线程中执行。
      • 可使用dispatch_get_main_queue()获得主队列。
    // 主队列的获取方法
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    • 对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)
      • 可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
    // 全局并发队列的获取方法
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    3.2 任务的创建方法

    GCD 提供了同步执行任务的创建方法dispatch_sync和异步执行任务创建方法dispatch_async

    // 同步执行任务创建方法
    dispatch_sync(queue, ^{
        // 这里放同步执行任务代码
    });
    // 异步执行任务创建方法
    dispatch_async(queue, ^{
        // 这里放异步执行任务代码
    });
    

    虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么我们就有了四种不同的组合方式。这四种不同的组合方式是:

    1. 同步执行 + 并发队列
    2. 异步执行 + 并发队列
    3. 同步执行 + 串行队列
    4. 异步执行 + 串行队列

    实际上,刚才还说了两种特殊队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。但是主队列因为有点特殊,所以我们就又多了两种组合方式。这样就有六种不同的组合方式了。

    1. 同步执行 + 主队列
    2. 异步执行 + 主队列

    那么这几种不同组合方式各有什么区别呢,这里为了方便,先上结果,再来讲解。你可以直接查看表格结果,然后跳过 4. GCD的基本使用

    区别 并发队列 串行队列 主队列
    同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 主线程调用:死锁卡住不执行
    其他线程调用:没有开启新线程,串行执行任务
    异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

    下边我们来分别讲讲这几种不同的组合方式的使用方法。

    4. GCD 的基本使用

    先来讲讲并发队列的两种执行方式。

    4.1 同步执行 + 并发队列

    • 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
    /**
     * 同步执行 + 并发队列
     * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
     */
    - (void)syncConcurrent {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"syncConcurrent---end");
    }
    

    输出结果:
    2018-02-23 20:34:55.095932+0800 YSC-GCD-demo[19892:4996930] currentThread---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:34:55.096086+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---begin
    2018-02-23 20:34:57.097589+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:34:59.099100+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:35:01.099843+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:35:03.101171+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:35:05.101750+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:35:07.102414+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
    2018-02-23 20:35:07.102575+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---end

    同步执行 + 并发队列中可看到:

    • 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。
    • 所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间执行的(同步任务需要等待队列的任务执行结束)。
    • 任务按顺序执行的。按顺序执行的原因:虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。

    4.2 异步执行 + 并发队列

    • 可以开启多个线程,任务交替(同时)执行。
    /**
     * 异步执行 + 并发队列
     * 特点:可以开启多个线程,任务交替(同时)执行。
     */
    - (void)asyncConcurrent {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"asyncConcurrent---end");
    }
    

    输出结果:
    2018-02-23 20:36:41.769269+0800 YSC-GCD-demo[19929:5005237] currentThread---<NSThread: 0x604000062d80>{number = 1, name = main}
    2018-02-23 20:36:41.769496+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---begin
    2018-02-23 20:36:41.769725+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---end
    2018-02-23 20:36:43.774442+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}
    2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
    2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
    2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
    2018-02-23 20:36:45.779302+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
    2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}

    异步执行 + 并发队列中可以看出:

    • 除了当前线程(主线程),系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。
    • 所有任务是在打印的syncConcurrent---beginsyncConcurrent---end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。

    接下来再来讲讲串行队列的两种执行方式。

    4.3 同步执行 + 串行队列

    • 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
    /**
     * 同步执行 + 串行队列
     * 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
     */
    - (void)syncSerial {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncSerial---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
        
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"syncSerial---end");
    }
    

    输出结果为:
    2018-02-23 20:39:37.876811+0800 YSC-GCD-demo[19975:5017162] currentThread---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:37.876998+0800 YSC-GCD-demo[19975:5017162] syncSerial---begin
    2018-02-23 20:39:39.878316+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:41.879829+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:43.880660+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:45.881265+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:47.882257+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:49.883008+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
    2018-02-23 20:39:49.883253+0800 YSC-GCD-demo[19975:5017162] syncSerial---end

    同步执行 + 串行队列可以看到:

    • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。
    • 所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
    • 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。

    4.4 异步执行 + 串行队列

    • 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
    /**
     * 异步执行 + 串行队列
     * 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
     */
    - (void)asyncSerial {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncSerial---begin");
        
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"asyncSerial---end");
    }
    

    输出结果为:
    2018-02-23 20:41:17.029999+0800 YSC-GCD-demo[20008:5024757] currentThread---<NSThread: 0x604000070440>{number = 1, name = main}
    2018-02-23 20:41:17.030212+0800 YSC-GCD-demo[20008:5024757] asyncSerial---begin
    2018-02-23 20:41:17.030364+0800 YSC-GCD-demo[20008:5024757] asyncSerial---end
    2018-02-23 20:41:19.035379+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
    2018-02-23 20:41:21.037140+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
    2018-02-23 20:41:23.042220+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
    2018-02-23 20:41:25.042971+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
    2018-02-23 20:41:27.047690+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}
    2018-02-23 20:41:29.052327+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}

    异步执行 + 串行队列可以看到:

    • 开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。
    • 所有任务是在打印的syncConcurrent---beginsyncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
    • 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。

    下边讲讲刚才我们提到过的特殊队列:主队列

    • 主队列:GCD自带的一种特殊的串行队列
      • 所有放在主队列中的任务,都会放到主线程中执行
      • 可使用dispatch_get_main_queue()获得主队列

    我们再来看看主队列的两种组合方式。

    4.5 同步执行 + 主队列

    同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。

    4.5.1 在主线程中调用同步执行 + 主队列

    • 互相等待卡住不可行
    /**
     * 同步执行 + 主队列
     * 特点(主线程调用):互等卡主不执行。
     * 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
     */
    - (void)syncMain {
        
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"syncMain---begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        dispatch_sync(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_sync(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"syncMain---end");
    }
    

    输出结果
    2018-02-23 20:42:36.842892+0800 YSC-GCD-demo[20041:5030982] currentThread---<NSThread: 0x600000078a00>{number = 1, name = main}
    2018-02-23 20:42:36.843050+0800 YSC-GCD-demo[20041:5030982] syncMain---begin
    (lldb)

    同步执行 + 主队列可以惊奇的发现:

    • 在主线程中使用同步执行 + 主队列,追加到主线程的任务1、任务2、任务3都不再执行了,而且syncMain---end也没有打印,在XCode 9上还会报崩溃。这是为什么呢?

    这是因为我们在主线程中执行syncMain方法,相当于把syncMain任务放到了主线程的队列中。而同步执行会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把任务1追加到主队列中,任务1就在等待主线程处理完syncMain任务。而syncMain任务需要等待任务1执行完毕,才能接着执行。

    那么,现在的情况就是syncMain任务和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain---end也没有打印。

    要是如果不在主线程中调用,而在其他线程中调用会如何呢?

    4.5.2 在其他线程中调用同步执行 + 主队列

    • 不会开启新线程,执行完一个任务,再执行下一个任务
    // 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行
     selector 任务
    [NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
    

    输出结果:
    2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)}
    2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin
    2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
    2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end

    在其他线程中使用同步执行 + 主队列可看到:

    • 所有任务都是在主线程(非当前线程)中执行的,没有开启新的线程(所有放在主队列中的任务,都会放到主线程中执行)。
    • 所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间执行(同步任务需要等待队列的任务执行结束)。
    • 任务是按顺序执行的(主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

    为什么现在就不会卡住了呢?
    因为syncMain 任务放到了其他线程里,而任务1任务2任务3都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其他线程中执行到追加任务1到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务2任务3。所以这里不会卡住线程。

    4.6 异步执行 + 主队列

    • 只在主线程中执行任务,执行完一个任务,再执行下一个任务。
    /**
     * 异步执行 + 主队列
     * 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
     */
    - (void)asyncMain {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncMain---begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"asyncMain---end");
    }
    

    输出结果:
    2018-02-23 20:45:49.981505+0800 YSC-GCD-demo[20111:5046708] currentThread---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:45:49.981935+0800 YSC-GCD-demo[20111:5046708] asyncMain---begin
    2018-02-23 20:45:49.982352+0800 YSC-GCD-demo[20111:5046708] asyncMain---end
    2018-02-23 20:45:51.991096+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:45:53.991959+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:45:55.992937+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:45:57.993649+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:45:59.994928+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}
    2018-02-23 20:46:01.995589+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}

    异步执行 + 主队列可以看到:

    • 所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。
    • 所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。
    • 任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。

    弄懂了难理解、绕来绕去的队列+任务之后,我们来学习一个简单的东西:5. GCD 线程间的通信

    5. GCD 线程间的通信

    在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

    /**
     * 线程间通信
     */
    - (void)communication {
        // 获取全局并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
        // 获取主队列
        dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
        
        dispatch_async(queue, ^{
            // 异步追加任务
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
            
            // 回到主线程
            dispatch_async(mainQueue, ^{
                // 追加在主线程中执行的任务
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            });
        });
    }
    

    输出结果:
    2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
    2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
    2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}

    • 可以看到在其他线程中先执行任务,执行完了之后回到主线程执行主线程的相应操作。

    6. GCD 的其他方法

    6.1 GCD 栅栏方法:dispatch_barrier_async

    • 我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。
      dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。具体如下图所示:
      dispatch_barrier_async.png
    /**
     * 栅栏方法 dispatch_barrier_async
     */
    - (void)barrier {
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        dispatch_async(queue, ^{
            // 追加任务4
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    }
    

    输出结果:
    2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
    2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
    2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}

    dispatch_barrier_async执行结果中可以看出:

    • 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。

    6.2 GCD 延时执行方法:dispatch_after

    我们经常会遇到这样的需求:在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。
    需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

    /**
     * 延时执行方法 dispatch_after
     */
    - (void)after {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"asyncMain---begin");
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 2.0秒后异步追加任务代码到主队列,并开始执行
            NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
        });
    }
    

    输出结果:
    2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main}
    2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin
    2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main}

    可以看出:在打印 asyncMain---begin 之后大约 2.0 秒的时间,打印了 after---<NSThread: 0x60000006ee00>{number = 1, name = main}

    6.3 GCD 一次性代码(只执行一次):dispatch_once

    • 我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用
      dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。
    /**
     * 一次性代码(只执行一次)dispatch_once
     */
    - (void)once {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 只执行1次的代码(这里面默认是线程安全的)
        });
    }
    

    6.4 GCD 快速迭代方法:dispatch_apply

    • 通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_applydispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

    如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
    我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
    还有一点,无论是在串行队列,还是异步队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。

    /**
     * 快速迭代方法 dispatch_apply
     */
    - (void)apply {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        NSLog(@"apply---begin");
        dispatch_apply(6, queue, ^(size_t index) {
            NSLog(@"%zd---%@",index, [NSThread currentThread]);
        });
        NSLog(@"apply---end");
    }
    

    输出结果:
    2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin
    2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
    2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}
    2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}
    2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}
    2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
    2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}
    2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end

    因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是apply---end一定在最后执行。这是因为dispatch_apply函数会等待全部任务执行完毕。

    6.5 GCD 队列组:dispatch_group

    有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

    • 调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现
      dispatch_group_async
    • 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。

    6.5.1 dispatch_group_notify

    • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
    /**
     * 队列组 dispatch_group_notify
     */
    - (void)groupNotify {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        
        dispatch_group_t group =  dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
            NSLog(@"group---end");
        });
    }
    

    输出结果:
    2018-02-23 22:05:03.790035+0800 YSC-GCD-demo[20494:5183349] currentThread---<NSThread: 0x604000072040>{number = 1, name = main}
    2018-02-23 22:05:03.790237+0800 YSC-GCD-demo[20494:5183349] group---begin
    2018-02-23 22:05:05.792721+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
    2018-02-23 22:05:05.792725+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
    2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
    2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
    2018-02-23 22:05:09.798717+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
    2018-02-23 22:05:11.799827+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
    2018-02-23 22:05:11.799977+0800 YSC-GCD-demo[20494:5183349] group---end

    dispatch_group_notify相关代码运行输出结果可以看出:
    当所有任务都执行完成之后,才执行dispatch_group_notify block 中的任务。

    6.5.2 dispatch_group_wait

    • 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
    /**
     * 队列组 dispatch_group_wait
     */
    - (void)groupWait {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        
        dispatch_group_t group =  dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
        NSLog(@"group---end");
    }
    

    输出结果:
    2018-02-23 22:10:16.939258+0800 YSC-GCD-demo[20538:5198871] currentThread---<NSThread: 0x600000066780>{number = 1, name = main}
    2018-02-23 22:10:16.939455+0800 YSC-GCD-demo[20538:5198871] group---begin
    2018-02-23 22:10:18.943862+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
    2018-02-23 22:10:18.943861+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
    2018-02-23 22:10:20.947787+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
    2018-02-23 22:10:20.947790+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
    2018-02-23 22:10:20.948134+0800 YSC-GCD-demo[20538:5198871] group---end

    dispatch_group_wait相关代码运行输出结果可以看出:
    当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。

    6.5.3 dispatch_group_enter、dispatch_group_leave

    • dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数+1
    • dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数-1。
    • 当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
    /**
     * 队列组 dispatch_group_enter、dispatch_group_leave
     */
    - (void)groupEnterAndLeave
    {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        
        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_async(queue, ^{
            // 追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            // 追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 等前面的异步操作都执行完毕后,回到主线程.
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
            NSLog(@"group---end");
        });
        
    //    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //
    //    NSLog(@"group---end");
    }
    

    输出结果:
    2018-02-23 22:14:17.997667+0800 YSC-GCD-demo[20592:5214830] currentThread---<NSThread: 0x604000066600>{number = 1, name = main}
    2018-02-23 22:14:17.997839+0800 YSC-GCD-demo[20592:5214830] group---begin
    2018-02-23 22:14:20.000298+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
    2018-02-23 22:14:20.000305+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
    2018-02-23 22:14:22.001323+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
    2018-02-23 22:14:22.001339+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
    2018-02-23 22:14:24.002321+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
    2018-02-23 22:14:26.002852+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
    2018-02-23 22:14:26.003116+0800 YSC-GCD-demo[20592:5214830] group---end

    dispatch_group_enter、dispatch_group_leave相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。这里的dispatch_group_enter、dispatch_group_leave组合,其实等同于dispatch_group_async

    6.6 GCD 信号量:dispatch_semaphore

    GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或大于1时,计数减1且不等待,可通过。
    Dispatch Semaphore 提供了三个函数。

    • dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
    • dispatch_semaphore_signal:发送一个信号,让信号总量加1
    • dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

    注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。

    Dispatch Semaphore 在实际开发中主要用于:

    • 保持线程同步,将异步执行任务转换为同步执行任务
    • 保证线程安全,为线程加锁

    6.6.1 Dispatch Semaphore 线程同步

    我们在开发中,会遇到这样的需求:异步执行耗时任务,并使用异步执行的结果进行一些额外的操作。换句话说,相当于,将将异步执行任务转换为同步执行任务。比如说:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法。通过引入信号量的方式,等待异步执行任务结果,获取到 tasks,然后再返回该 tasks。

    - (NSArray *)tasksForKeyPath:(NSString *)keyPath {
        __block NSArray *tasks = nil;
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
            if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
                tasks = dataTasks;
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
                tasks = uploadTasks;
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
                tasks = downloadTasks;
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
                tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
            }
    
            dispatch_semaphore_signal(semaphore);
        }];
    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
        return tasks;
    }
    

    下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步执行任务转换为同步执行任务。

    /**
     * semaphore 线程同步
     */
    - (void)semaphoreSync {
        
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"semaphore---begin");
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        
        __block int number = 0;
        dispatch_async(queue, ^{
            // 追加任务1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            
            number = 100;
            
            dispatch_semaphore_signal(semaphore);
        });
        
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore---end,number = %zd",number);
    }
    

    输出结果:
    2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main}
    2018-02-23 22:22:26.521869+0800 YSC-GCD-demo[20642:5246341] semaphore---begin
    2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)}
    2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100

    从 Dispatch Semaphore 实现线程同步的代码可以看到:

    • semaphore---end 是在执行完 number = 100; 之后才打印的。而且输出结果 number 为 100。
      这是因为异步执行不会做任何等待,可以继续执行任务。异步执行将任务1追加到队列之后,不做等待,接着执行dispatch_semaphore_wait方法。此时 semaphore == 0,当前线程进入等待状态。然后,异步任务1开始执行。任务1执行到dispatch_semaphore_signal之后,总信号量,此时 semaphore == 1,dispatch_semaphore_wait方法使总信号量减1,正在被阻塞的线程(主线程)恢复继续执行。最后打印semaphore---end,number = 100。这样就实现了线程同步,将异步执行任务转换为同步执行任务。

    6.6.2 Dispatch Semaphore 线程安全和线程同步(为线程加锁)

    线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

    线程同步:可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。

    举个简单例子就是:两个人在一起聊天。两个人不能同时说话,避免听不清(操作冲突)。等一个人说完(一个线程结束操作),另一个再说(另一个线程再开始操作)。

    下面,我们模拟火车票售卖的方式,实现 NSThread 线程安全和解决线程同步问题。

    场景:总共有50张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。

    6.6.2.1 非线程安全(不使用 semaphore)

    先来看看不考虑线程安全的代码:

    /**
     * 非线程安全:不使用 semaphore
     * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
     */
    - (void)initTicketStatusNotSave {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"semaphore---begin");
        
        self.ticketSurplusCount = 50;
        
        // queue1 代表北京火车票售卖窗口
        dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
        // queue2 代表上海火车票售卖窗口
        dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(queue1, ^{
            [weakSelf saleTicketNotSafe];
        });
        
        dispatch_async(queue2, ^{
            [weakSelf saleTicketNotSafe];
        });
    }
    
    /**
     * 售卖火车票(非线程安全)
     */
    - (void)saleTicketNotSafe {
        while (1) {
            
            if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
                self.ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
                [NSThread sleepForTimeInterval:0.2];
            } else { //如果已卖完,关闭售票窗口
                NSLog(@"所有火车票均已售完");
                break;
            }
            
        }
    }
    

    输出结果(部分):
    2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main}
    2018-02-23 22:25:35.789260+0800 YSC-GCD-demo[20712:5258914] semaphore---begin
    2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] 剩余票数:48 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
    2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] 剩余票数:49 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
    2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] 剩余票数:47 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
    2018-02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] 剩余票数:46 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
    2018-02-23 22:25:36.198993+0800 YSC-GCD-demo[20712:5259176] 剩余票数:45 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
    ...

    可以看到在不考虑线程安全,不使用 semaphore 的情况下,得到票数是错乱的,这样显然不符合我们的需求,所以我们需要考虑线程安全问题。

    6.6.2.2 线程安全(使用 semaphore 加锁)

    考虑线程安全的代码:

    /**
     * 线程安全:使用 semaphore 加锁
     * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票
     */
    - (void)initTicketStatusSave {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"semaphore---begin");
        
        semaphoreLock = dispatch_semaphore_create(1);
        
        self.ticketSurplusCount = 50;
        
        // queue1 代表北京火车票售卖窗口
        dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
        // queue2 代表上海火车票售卖窗口
        dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(queue1, ^{
            [weakSelf saleTicketSafe];
        });
        
        dispatch_async(queue2, ^{
            [weakSelf saleTicketSafe];
        });
    }
    
    /**
     * 售卖火车票(线程安全)
     */
    - (void)saleTicketSafe {
        while (1) {
            // 相当于加锁
            dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
            
            if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
                self.ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
                [NSThread sleepForTimeInterval:0.2];
            } else { //如果已卖完,关闭售票窗口
                NSLog(@"所有火车票均已售完");
                
                // 相当于解锁
                dispatch_semaphore_signal(semaphoreLock);
                break;
            }
            
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
        }
    }
    

    输出结果为:
    2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}
    2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin
    2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票数:49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票数:48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票数:47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    ...
    2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票数:4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票数:3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票数:2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票数:1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
    2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票数:0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
    2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火车票均已售完
    2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火车票均已售完

    可以看出,在考虑了线程安全的情况下,使用 dispatch_semaphore
    机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。


    参考资料:



    iOS多线程详尽总结系列文章:


    相关文章

      网友评论

      • 782aa413fd27:小哥,能留一下你的QQ吗?我问点GCD问题,这里说不太方便,比较啰嗦:smile:
      • a90a147abe6d:大神,图是用什么画的?
        ITCharge:@摩西摩西和辛巴 用的 Keynote
      • 瘦子书生:大神,我试了一下,异步+串行队列,是只开启一个新线程吗
      • 782aa413fd27:请问下这段代码 NSLog(@"1");
        dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
        });
        NSLog(@"3");
        打印输出1,2和3不会被打印。我的问题是:3在主队列中吗?就是没有被执行到的代码,会提前加到主队列任务中吗?
        782aa413fd27:@行走的少年郎 咱俩理解的是一样一样滴:smile:
        ITCharge:先回答问题:3 应该还没添加到主队列中,因为 第2-4行代码 执行的时候阻塞了主线程。
        原因:
        代码在默认情况下是在主线程下执行的。
        你的代码中,第一行:『打印输出1』 是在主线程。
        然后第2-4行:将『打印输出2』的操作添加到主队列中同步执行。而主队列还没执行完毕(在执行当前代码),主线程要等到你执行完 『打印输出2』,才能接着往下执行。
        而这个添加到主队列同步执行的操作 也在等待主线程执行完毕。 这两个都在等对方执行完毕。所以就把主线程阻塞了。
      • 782aa413fd27:这篇文章是我看的GCD整理比较详细的一篇文章,感谢分享!
        ITCharge:哈哈 谢谢评价 不客气哟~~
      • 安静就好_:看了好几遍了,点个赞。
        ITCharge:@你太善良_ :kissing_heart:
      • 換季:需求一个页面2个请求 tableView更新一次就行。
        使用group类型,AFN网络请求,在dispatch_group_notify里【self.tableview reloadData】;会有bug(只能在AFN返回block里 reloadData才能更新) 否则数据为空;像这种情况该怎么做呢?
        ITCharge:@換季 是啊。因为有 AFN 开多线程了,所以就不用group开了。
        換季:@行走的少年郎 意思是不用使用group这个多线程了?
        ITCharge:AFN 是异步请求。你是不是把 AFN 的请求放到了 dispatch_async 里边?
        这样的话 是达不到同步的要求的。
        dispatch_async 里边如果是 AFN 异步请求的话,在 AFN 异步请求发出的那一瞬间,dispatch_async 就视作已经执行完 AFN 的请求(不等待执行完毕),接着往下进行了。
        所以根本起不到同步的作用。
        这个其实很简单。你全局设置一个标记量,代表任务数。比如两个 AFN 请求,就设置为 2.
        在 AFN 请求回来之后让 标记的任务数 减去1。 当任务数为0的话 在刷新。
        类似这样

        int flag = 2;
        发送 请求1 {
        block 回调中:{
        flag--;
        if(flag == 0) {
        // 说明两个任务执行完了,刷新 tableview
        }
        }
        }
        发送 请求2 {
        block 回调中:{
        flag--;
        if(flag == 0) {
        // 说明两个任务执行完了,刷新 tableview
        }
        }
        }

        类似这样的伪代码。你可以试试看。
      • 请叫我小白同学:你好博主,在主线程中调用 同步执行 + 主队列那块 方面讲的有点侥,是否可以这样理解,之所以死锁,首先获取主队列,然后sys任务1、2、3添加到主队列中,然而主队列在主线程上,正在被主线程执行,不巧的是sys里任务等待主线程任务执行完才执行,也不巧的是,主线程的正在执行的任务,等待sys任务执行完才执行.
        可以是否这样理解? 不过我对你写对sys理解,有点偏差,之前我对理解是 “只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行” .
        所以侥不出来,能否解答一下?
        请叫我小白同学:博主,再打扰一下,我再请教一下.
        比如《5. GCD 线程间的通信》这块
        如果引用AFN网络请求框架,刚好应用到GCD的话
        dispatch_async(mainQueue,){},这段代码,为什么要放到AFN回调block的里面变更UI,放到block外面会出现一些的bug呢?能否指点一下?
        请叫我小白同学:@行走的少年郎 谢谢指点,博主写的很详细,就这块,我读了好几遍,也想好久,也不知道理解正否,所以前来请教.
        ITCharge:你好。
        关于『同步执行 + 主队列』造成死锁,确实和你理解的一样,按你理解的来就好了。这边我写的确实有点绕,我考虑再改改。
        第二个关于 sync 的理解,你之前的理解,我感觉很对啊。比我总结的感觉还要好一点。

        我觉得同步与否的关键是:是否等待 block 中的任务执行完成。sysn 等待当前任务完成,完成之后才向下执行。asycn 可以不等待任务执行完毕,继续向下执行。而『同步』这种实现的方式是通过阻塞当前线程来实现的。这一点,我没有写出来。
        当然,我个人是这样子认为的,不知道是否还有偏差?
      • b8e6682d009f:@行走的少年郎,大大我又来问问题了,为什么6一定会比5先执行呢。
        dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"5");//异步主线程主队列
        });
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
        ^{
        NSLog(@"6");//异步子线程
        });
        ITCharge:并不是一定噢。 我刚才试过了。这段代码在 viewdidload 结果和你一样。
        放到 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 里边点击之后执行就变了。 且多次尝试之后发现顺序并是不一定。

        这个方式无法保证执行的先后的顺序的。 可以考虑用dispatch_semaphore、dispatch_group来保证执行顺序。
      • b8e6682d009f:@行走的少年郎,首先撒花感谢大大,隔了很长时间看完最后的线程安全,有个问题:像买票和购物余量。如果像这样,线程应该会更多(北上广深等等等)不可能代码这样写吧,好像我原来购物项目就是简单的点击以后去请求后台接口,后台余量不足就提示。感觉12306也没有这样吧。
        ITCharge:@蓝涩枫 卖票那个只是举个例子。。。不要太当真啦。
        主队列主要用来处理所有的和UI的操作(刷新或者点击按钮)。所以耗时操作不建议放到主队列中。可以放到并发队列、串行队列中,异步执行。这样不会阻塞主线程。
        for 循环遍历可以尝试使用 dispatch_apply。注意:同样也不建议放到主队列中,可以放到全局并发队列中。
        b8e6682d009f:@行走的少年郎,还有就是像常用的延时和for循环遍历,我是不是都可以改成主队列的gcd方法了,处理应该会快的哦。
      • liangZhen:楼主你好,请教一个问题,
        dispatch_barrier_async()
        为什么对
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

        不起作用了?
        ITCharge:@liangZhen 天啊。我也试过了。。。确实如你所说 使用dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 在这里并没有得到应有的效果。。
        这个。。。我也有点懵逼。不知道为啥会这样
        liangZhen:@行走的少年郎 感谢回复
        dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(globalQueue, ^{
        [self taskName:@"task-1" lastTime:5.0];
        });

        dispatch_async(globalQueue, ^{
        [self taskName:@"task-2" lastTime:8.0];
        });

        dispatch_barrier_async(globalQueue, ^{
        NSLog(@"GCD栅栏:上面的任务完成");
        });

        dispatch_async(globalQueue, ^{
        [self taskName:@"task-3" lastTime:5.0];
        });

        dispatch_async(globalQueue, ^{
        [self taskName:@"task-4" lastTime:3.0];
        });

        - (void)taskName:(NSString *)name lastTime:(CGFloat)time{
        NSLog(@"%@--start---持续%fs--%@", name, time, [NSThread currentThread]);
        // NSLog(@"%@",[NSThread currentThread]);
        while (time > 0) {
        NSLog(@"%@--还需:%f秒",name, time);
        [NSThread sleepForTimeInterval:1.0];
        time = time - 1.0;
        }
        NSLog(@"%@--任务完成",name);
        }
        ITCharge:能否看下代码
      • 梁森的简书:个人觉得可以分为几篇文章来写,每篇文章有一个重点就好,重点突出,内容精简
      • 丐帮头:问下:同步任务不会开启新线程,那就只能主线程了,在其他队列里面可以直接调用主线程吗,难道主线程不是由主队列管理的吗?譬如:同步+串行队列
      • astring:同样是九年义务教育,你咋这么优秀呢
        astring:@行走的少年郎 大神那个模拟卖票信号锁的窗口 为啥我的都在同一个窗口?
        ITCharge:@astring 也许食堂伙食好吧:flushed:
      • biboba:关于dispatch_apply那个例子,好像一直是在主线程中顺序执行的,并没有出现文中的结果
        ITCharge:@焦虑不安 我又测试了一下,并没有你说的这种情况出现。 可否贴出来代码和结果看看?
        biboba:@行走的少年郎 我是直接复制你的代码,-(void)apply方法在 viewwillappear 方法里面运行
        ITCharge:你好,可以贴出代码看看吗
      • biboba:为什么普通串行队列+同步执行不会死锁呢,不都是在主线程中运行吗
        ITCharge:主线程中也可以创建多个队列,而不只是一个主队列。 普通串行队列,相当于在 主线程创建了一个串行队列。最外层的方法 放在主队列中执行。 而添加到队列中的操作,是在创建的串行队列中执行的。所以不会卡死。
      • f154c152a213:主队列其实并没什么特殊的,就是一个普通的串行队列。
        1、同步操作时,在同一个串行队列中对当前队列sync操作都会导致死锁,
        2、异步操作时,如果在当前队列async,并不会开启新线程;在其他队列当中再对该串行队列进行asyn操作会开启新线程
        那个表得改一改,不然会有误导:blush:
      • Tonyliu_:想问一下,异步执行的主队列为什么不会死锁呢
        如果同步执行的主队列的执行顺序是 :同步队列里有任务2 1-> 同步队列 ->3->2 形成了死锁,那异步的主队列的执行情况是什么样呢
        炎成:@行走的少年郎 我搞错了:flushed:
        ITCharge:@炎成 ???我的看法有些不同。异步执行 + 主队列 不会开启新线程吧。然后文章里 没有说 异步执行 + 主队列 会造成死锁。只是说 在主线程中调用 同步执行 + 主队列 会造成死锁。
        Tonyliu_:最后搞懂了
      • 凉风起君子意如何:竟然是第568个点赞的人,好开心:blush:
        ITCharge:哈哈 谢谢谢谢~
      • 纳木错_grace:谢谢作者的总结,想请问下,那个卖票的线程安全的确定吗?我按照你的代码测了下,加了信号量也不一定安全。我去掉了信号量,加上了同步锁后,就没有再测试出有问题了。
        ITCharge:@纳木错_grace :smile: 没事的~~~最后解决了问题就好。
        纳木错_grace:@行走的少年郎 谢谢回复,抱歉啊!我把信号量的创建 的代码地方写错了。导致出错。你的是正确的。
        ITCharge:O__O "… 是直接粘贴的我的代码吗? 如果是的话,我再测一下。
        如果不是的话。。能否让我看看你测试的代码?
      • 9c6a0d1200d6:NB
        ITCharge::smile: 过奖啦
      • 心亦逸风:dispatch_apply是同步+并发队列吧
        心亦逸风:@行走的少年郎 🙏
        ITCharge:如有不妥之处,还望之处,感激不尽。
        ITCharge:原文确实有问题,感谢指出。不过,我重新试了下,对于 dispatch_apply 同步、异步 我有着不同的看法。
        这里我的理解是 如果 dispatch_apply 所在队列是 串行队列,则 dispatch_apply 是串行同步执行(在调用线程中按顺序执行,没有开辟新线程,所以不是异步)。
        如果队列是 异步队列,则 dispatch_apply 是异步并发执行(可以看到打印的线程是在多个线程中,且各项之间顺序不定,说明并没有等待当前操作完成,从而说明了是异步执行)。
        而 end 之所以会在最后执行,我个人猜测,内部处理就像是调用了 group 里边的 dispatch_group_wait 方法一样。会等待 block 指定次数的任务全部完成之后,才执行后边的方法。
      • xxttw:使用信号量 模拟火车票售卖这里.我需要在 while 循环中添加一个 dispatch_semaphore_signal(semaphoreTicket); 才会继续执行.不然就只打印一次. 为啥.
        xxttw:@行走的少年郎 - (void)sellTicket
        {
        NSLog(@"sellTicket---%@",[NSThread currentThread]); // 打印当前线程
        NSLog(@"sellTicket---begin");
        __weak typeof(self) weakSelf = self;
        self.ticketCount = 50;

        dispatch_semaphore_t semaphoreTicket = dispatch_semaphore_create(1);

        dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);

        dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);

        dispatch_async(queue1, ^{
        [weakSelf ticket:semaphoreTicket];
        });

        dispatch_async(queue2, ^{
        [weakSelf ticket:semaphoreTicket];

        });

        }

        - (void)ticket:(dispatch_semaphore_t)semaphoreTicket
        {
        while (1) {

        // 某一异步线程进入时,使信号量-1 信号量为0 阻塞其他线程进入
        dispatch_semaphore_wait(semaphoreTicket, DISPATCH_TIME_FOREVER);
        if (self.ticketCount > 0) {
        self.ticketCount--;
        NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketCount, [NSThread currentThread]]);
        // 执行完毕后.信号量+1 恢复线程阻塞
        // dispatch_semaphore_signal(semaphoreTicket);

        } else {
        NSLog(@"所有火车票均已售完");
        dispatch_semaphore_signal(semaphoreTicket);
        break;
        }
        }
        dispatch_semaphore_signal(semaphoreTicket);
        }
        ITCharge:能提供下 整个 模拟火车票售卖的代码吗? 包括 while 循环的。
        xxttw:if (self.ticketCount > 0) {
        self.ticketCount--;
        NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketCount, [NSThread currentThread]]);
        dispatch_semaphore_signal(semaphoreTicket);

        } else {
        NSLog(@"所有火车票均已售完");
        dispatch_semaphore_signal(semaphoreTicket);
        break;
        }
      • MacLeon:详见恨晚啊,对我帮助很大,楼主辛苦了~
        ITCharge::smile: 谢谢~~~~对你有帮助就好~~~这是对我莫大的鼓励
      • Sephiroth_Ma:线程间的通讯这个 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        for (int i = 0; i<100; ++i) {

        NSLog(@"------%d",i);
        }

        });
        //回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{

        NSLog(@"回到了主线程");

        });
        在分线程里执行时是不是得同步执行呢?如果异步的话分线程没有执行完毕就回到主线程了。。
        2017-11-20 15:16:28.055966+0800 GCD[75256:6430941] ------6
        2017-11-20 15:16:28.056921+0800 GCD[75256:6430941] ------7
        2017-11-20 15:16:28.057244+0800 GCD[75256:6430941] ------8
        2017-11-20 15:16:28.057857+0800 GCD[75256:6430941] ------9
        2017-11-20 15:16:28.058505+0800 GCD[75256:6430578] 回到了主线程
        2017-11-20 15:16:28.060174+0800 GCD[75256:6430941] ------10
        2017-11-20 15:16:28.060742+0800 GCD[75256:6430941] ------11
        Sephiroth_Ma:@行走的少年郎 :pray: 原来如此,thank you
        ITCharge:我看了你写的代码,你回到主线程的代码 写在了外边。等于开了两个异步线程。。。一个子线程,一个主线程。 正确代码如下:
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i<100; ++i) {
        NSLog(@"------%d",i);
        }
        //回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"回到了主线程");
        });

        });
      • 李天迪装修美缝经历:学习了,希望可以与你互动~
        ITCharge:@李天迪装修美缝经历 好啊,一起交流哈。
      • IMKel:文章写得真棒,排版也好看,真赞👍
        IMKel:加油
        ITCharge:谢谢夸奖~我会继续努力的。
      • Self_Time:为了项目流畅,基本常用的都是开辟异步线程为主,回到主线程刷新UI为主。还有延时的方法和队列组的执行完的通知都是很实用的。很全面!
        ITCharge::smile: 实用就好。
      • 肖无情:要是如果不再主线程中调用,而在其他线程中调用会如何呢?

        不会开启新线程,执行完一个任务,再执行下一个任务(在其他线程中调用)
        dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

        dispatch_async(queue, ^{
        [self syncMain];
        }
        肖无情:这个例子似乎并不能证明其他线程添加同步任务不会阻塞线程的问题
      • Claire_wu:- (IBAction)asyncConcurrent:(id)sender {
        NSLog(@"asyncConcurrent---begin");

        dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

        dispatch_async(queue, ^{
        for (int i = 0; i < 1000; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
        }
        });
        NSLog(@"asyncConcurrent---middle1");
        [NSThread sleepForTimeInterval:1.0f];
        dispatch_async(queue, ^{
        for (int i = 0; i < 1000; ++i) {
        NSLog(@"2------%@",[NSThread currentThread]);
        }
        });
        NSLog(@"asyncConcurrent---middle2");
        [NSThread sleepForTimeInterval:1.0f];
        dispatch_async(queue, ^{
        for (int i = 0; i < 1000; ++i) {
        NSLog(@"3------%@",[NSThread currentThread]);
        }
        });
        NSLog(@"asyncConcurrent---end");
        }

        在中间加两个sleep,就很容易得到middle和end的输出是穿插在中间的,楼主所说的所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行,这句是错的吧
        ITCharge:抱歉,知道今天才开始重新梳理这篇文章。
        这句话确实有问题。当时思考的不够深入,写的也不够仔细。
        异步执行并不是将任务完全添加到队列之后才开始执行。如果当前线程中有耗时操作,异步操作会在子线程中开始执行,至于执行时间,是由系统决定的。
        我觉得原文的话应该改成: 任务不一定是立即执行,很有可能在当前线程操作完之后才开始执行,也有可能在当前线程进行耗时操作的时候执行,至于什么时候开始执行,取决于当前线程是否存在耗时操作,当前线程阻塞程度,系统判断等等。
        不知这样还有问题吗?
      • 张大伟_a91e:谢谢学习了!
        ITCharge:不客气~有帮助就好。
      • bba562d26c39:博主有代码可以共享一下么
      • 开发仔小广:添加一下GCD 信号量控制并发 (dispatch_semaphore) 这个的使用呗
      • iOS大神进阶:并发应该都是并行吧,并发跟并行还是不一样的,具体的作者可以查一查。
        ITCharge:时隔大半年之后再看当初写的这篇文章,另有一番感悟。文章中的队列的确应该是 并发队列。
        并发的关键是你有处理多个任务的能力,不一定要同时。
        并行的关键是你有同时处理多个任务的能力。
        比如单 CPU 的电脑,可以使用分时交替运行的方式,模拟出并发执行任务。但是不代表着单 CPU 可以同时处理多个任务。
        ITCharge:感谢指出,之前还真没发现文章里边写的不对。
      • 强子ly:哎,本想写一个gcd专题来着,看完你这篇文章后居然让我无从下手了,仿佛每写一个字都是从你这copy的
      • YYT1992:写的真好,我这么笨的人竟然能看懂了:smiley:
        ITCharge:@YYT1992 :smile: 有帮助就好。谢谢夸奖
      • 强子ly:老铁,排版很nb啊,转载了啊,注:转载自 行走的少年郎
        ITCharge:转载请标注上 原文链接。 谢谢。
      • 进击的秃瓢:感觉写的很清楚 学习中
      • astring:线程执行完毕会自动回收吗?如果任务一直未执行完是否会造成性能下降?
      • 谢谢生活:楼主请教你一个问题,子线程耗时操作就只能采取block获取代码的回调方式吗?//RSA加密
        +(NSString*)RSAEnCodeStr:(NSString*)str{
        [self shareRSATool];
        NSString *string = [rsaTool.rsaEncryptor rsaEncryptString:str];
        return string;
        }想这种加密非常耗时 我想把rsaEncryptString搞成多线程,等执行完了在返回string怎么搞啊 ,谢谢了。
        谢谢生活:@行走的少年郎 谢谢
        ITCharge:rsaEncryptString方法名加上 block 参数 block回调中传入参数: str 在block回调里边做事情
        参考这里

        第一种:
        @propery(strong,nomatic> UIImage* img;
        ...
        NSURL *url = [NSURL URLWithString:@"http://photo.4228999.com/upload/photos/1417486049541.jpg&quot;];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
        self.img= [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:url]];
        });

        第二种:
        -(void)getNetImage:(void ^()(UIImage * image))block{

        NSURL *url = [NSURL URLWithString:@"http://photo.4228999.com/upload/photos/1417486049541.jpg&quot;];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
        UIImage *imga = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:url]];
        block(imga);

        });
        }
      • 谢谢生活:感觉楼主分享这么全的GCD
        ITCharge:@谢谢生活 不客气 对你有帮助就好
      • be0d8c256bea:你好 为什么同样都是在主线程下的串行+同步不会死锁
      • my_杨哥:楼主问你个问题,异步并行开启的多个线程,当任务执行完毕之后,开启的这几个线程会怎样?会自动关闭吗?还是会一直存在?
        f652ceeef770:开篇都说了,GCD自动管理线程的生命周期,内存等。。。认点儿真
      • ricefun:楼主你好 ,能说下GCD栅栏技术和NSOperation Dependency 依赖,这两个方法有什么区别吗,谢谢?
      • 一只不安分的程序猿:感谢博主的分享,之前一直不太懂,很详细,很透彻
      • miku酱啦:关于串行队列 异步任务这里
        另一方面可以看出,所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

        //我认为是 因为 打印begin 和end 和任务中的打印不在 一个线程中 一个主线程 一个子线程所以才会这样吧 多打印几次 end出现的位置 会有不同的结果吧
      • angry_zxy:我写了一个for循环,在循环里面异步调用并发队列,模拟器和部分真机可以开多个线程,ipad上却只能开两个线程,即使是那两个线程阻塞,也会等待不去开启第三个线程,我并没有设置最大并发数啊,想不通啊,我需要开很多线程,望指教!
      • LD_左岸:快速迭代的话 这么搞出来是没顺序的 如果我换成串行队形 那么他的效率是不是和普通for循环一样了呢?
        谢谢生活:不会,只能说不在主线程,不卡顿UI。
      • 经天纬地:5. GCD的队列组 dispatch_group

        有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。
        dispatch_group_t group = dispatch_group_create();

        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        });

        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        });

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
        });

        这个问题并没有解决,如果在任务中只是纯粹执行打印的操作那肯定看不出问题.可以在其中一个任务加一个延时操作,就不能实现前面两个操作执行完毕再执行主线程的任务了
        suxx0:@行走的少年郎 楼主的代码没问题。我这里是group中添加了两个异步的上传操作,上传完成有回调,两个都上传完成才执行下一步操作。为什么我这里老是回调还没回来,就执行了notify中的内容了呢?有可能是哪里出问题了啊?谢谢楼主!
        ITCharge:@suxx0 我代码中加了延时操作了啊,还是先执行的前两个操作,最后再执行主线程任务。
        试下我的代码看看是否还有这样的问题。如果有的话。。。我再看看咋回事
        suxx0:我也遇到这个问题了,怎么回事啊请问
      • bea0187814bb:写的真好,简洁易懂
        ITCharge:@smile刺客 哈哈,简单易懂就好。不客气。
        smile刺客:是我看过最详细最易懂的一篇讲解GCD的文章 ,感谢作者。:stuck_out_tongue_winking_eye:
        ITCharge:@没有色彩的多奇博 谢谢,对您有帮助就好。
      • RaInVis:2. 并发队列 + 异步执行
        您最后写得“另一方面可以看出,所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。”
        最后一句,加入任务队列后同步执行还是异步执行呢?
        ITCharge:@RaInVis 异步执行,不好意思啊。原来写错了,已改正。
      • b8e6682d009f:关于线程和队列有些问题,1、首先主线程包含多个队列,基本的方法(不使用多线程的操作)都是在主队列中完成的,每个队列中可以有多个子线程。2、在同一个队列中,同一个线程中的任务互相等待才会造成锁死;同一个队列,同一个线程中的任务下面需等待上面执行为阻塞。不知道这几句话说的对不对?
        -(void)deadLock1{
        dispatch_queue_t queue = dispatch_queue_create("com.demo.serialqueue", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"1");
        dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_sync(queue, ^{
        NSLog(@"3");
        });
        NSLog(@"4");
        });
        NSLog(@"5");
        }
        这个方法我试验过,如果是串行是会锁死的,并行不会,因为3是在主线程并行队列中,4是在并行队列中的子线程,两者不在一个线程中。不知道理解的对不对?
        b8e6682d009f:@行走的少年郎,楼主大大,看看这句对不对。同一个线程,同一个队列中互相等待才会造成死锁。
        每天进步0点1:1.不论串并,dispatch_sync会使当前线程进入“等待”,也就是暂停其他任务,执行完“3”才能继续其他的任务。但是目前的任务4比3先加入线程queue,遇到dispatch_sync时,要执行完3才能执行4,但是目前线程执行完4才能开始执行3,这样就行程了互相等待死锁。
        2.当前同步异步执行的是同一条线程queue,如果“”dispatch_async(queue”,queue改为get_main_queue,这个死锁就破了。
        甘_小腾:应该不对 我的理解: 这个3是同步执行+并发 是在当前的线程中 也就是2所在的线程 而2是异步+并发开辟线程 所以3不在主线程中
      • 李昭宏:很好,很详细,感谢作者~
        ITCharge:@昭宏李哈哈哈 不客气,对你有帮助就好:smile:
      • 怀念裸奔的童年:写的很透彻,不错
        ITCharge:@怀念裸奔的童年 谢谢评价:smile:
      • xclidongbo:精彩
        ITCharge:@xclidongbo 哈哈,看到你点的文章喜欢了,好开心。
        ITCharge:@xclidongbo 谢谢:smile::smile::smile:
      • 顾泠轩:0基础向博主学习的GCD,首先感谢博主分享。
        请问下楼主是经过多次测试,异步并发队列都是在begin和end之后才才开始执行吗?我这边测的,end是随机穿插在其他线程中间执行的。
        然后,我觉得主队列串行的解释似乎也存在一点问题。因为同步串行的时候,其实最终也是将队列加入到了主线程,主线程在接收队列后就开始执行添加的队列,此时同步串行方法也还没执行结束,但是线程并没有卡住。按楼主的说法,这个地方也是队列加到主线程,但主线程调用的方法还没结束,所以应该卡住才对。
        不知道我表达清楚没有。
        另外,所以我猜测,应该要区分开主线程和主队列吧。主队列正在执行添加自身的动作,所以卡住?
        这个是通过同步串行对比的猜测,理解错误的话,还请楼主告知,谢谢!
        顾泠轩:@行走的少年郎 昨天可能写错了,今天测试的异步并行的确和你的结果一样,抱歉啊,闹个乌龙。主队列那个也大概理解了,谢谢。
        ITCharge:@行走的少年郎 关于syncMain方法阻塞主队列,导致UI刷新不了,主线程卡死的情况,你可以在Main.storyboard添加一个UITextView控件,然后在touchesBegan:中调用syncMain方法(主队列+同步执行)测试一下。
        你会发现除了只打印begin,然后整个界面也卡死了。
        ITCharge:@顾泠轩 特别谢谢你能如此认真的阅读学习我的文章,并提出自己的理解和建议。我的回答如下:
        1. 通过在touchesBegan:中调用异步并发队列,多次测试都是在begin和end之后才开始执行的。并没有发现你说的随机穿插的现象,这里我也没想明白你出现的这种情况是为什么,猜测是因为XCode版本不同?
        2. 第二个,你的理解没有什么问题,可能是我文章里边表述不太清楚,我这里解释一下:
        先来理解主线程和主队列:主线程只有唯一一个主队列,但是线程可以创建多个队列。
        下边我来详细对比下『串行队列 + 同步执行方法』和主线程中调用『主队列+同步执行』的区别。看完你可能就明白了。
        syncSerial(串行队列 + 同步执行方法)其实是将syncSerial加入到了主线程之中的主队列,主队列执行syncSerial方法,分为以下几步:
        1. 先打印begin。
        2. syncSerial方法中又创建了一个自定义串行队列,这样主线程中就有两个队列了,一个主队列,还有一个自定义串行队列。
        3. syncSerial方法再将三个任务依次加入到了这个自定义串行队列中,并在加入的时候立即执行。自定义队列执行完这三个任务之后,自定义队列中的任务就执行完了。
        4. 主队列接着打印end,结果syncSerial方法执行完毕,主队列中的任务就执行完毕了。
        从中,可以看出:主队列中只有syncSerial方法这一个任务而已,其他任务都在自定义串行队列中。所以主队列没有被卡住,UI还能刷新,主线程还能干其他事,所以这里主线程就不会被卡住。
        而我们再来看syncMain方法(主队列+同步执行)在主线程调用的情况。
        syncMain方法是将syncMain方法加入了主队列,注:此时主队列中有一个syncMain方法的任务。然后主队列执行syncMain方法,分为以下几步:
        1. 先打印begin。
        2. 将第一个自己创建的任务加入到主队列中,并立即执行。
        但是,这时候主队列中除了第一个创建的任务之外,前边还有一个syncMain方法的任务等待执行啊。 这时候,第一个创建的任务就只能等待前边的syncMain方法任务执行完毕了。但是syncMain方法的任务在等待第一个创建的任务执行啊,所以主队列就卡住了。可是,要知道:主队列可是用来更新UI的啊,而且主线程中就只有唯一一个主队列,主队列一卡住,这下可好,UI也刷新不了了。如果当前界面上有UITextView、UITableView等等各种控件,也就都卡死了,所以主线程就卡死了。
        这样不知道是否表达足够清晰明了,如果我的表述还有哪些问题,请再指出,谢谢。
      • 深山老林飞:好详细
        ITCharge:@深山老林飞 :smile:喜欢就好
      • 高压玩笑:2. 并发队列 + 异步执行 那里 是在begin跟end之间执行的 并不是在end之后
        Mikebanana:明明是在end后面啊
        RaInVis:@行走的少年郎 你for循环的次数改大一点,千位数,再试试
        ITCharge:@高压玩笑 我又测试了多次,通过在touchesBegan:中调用并发异步方法,发现都是在begin和end之后才开始调用的啊。
      • 8df99919a8f7:写的深入,通俗易懂!赞
        ITCharge:@大湖鱼儿多 :smile:
      • e0044b834c7b:很有用
        ITCharge:@小凡15 :smile: 有用就好。
      • d9b8f0c0bc9b:请问有没有子线程和子线程之间的通信
        ITCharge:@羽之123 在子线程的队列中,再添加一个子线程队列应该就算是子线程之间的通信了把。
      • 新地球说着一口陌生腔调:写的详细
        ITCharge:@新地球说着一口陌生腔调 谢谢评价
      • 脚下的斑马线:你好像写错了,3. 串行队列 + 同步执行 2.另一方面可以看出,所有任务是在打印的syncConcurrent---begin和syncConcurrent---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。
        ITCharge:@没打伞的鱼 确实是写错了,谢谢指正。
        ITCharge:@城北小客 十分谢谢纠错指正,我已经改过来了。
        没打伞的鱼:@城北小客 我也觉得是写错了
      • 刘建国大好人:为什么我的代码图片不能滑动
        ITCharge:@刘建国大好人 哦哦,你说的简书文章上的代码吗,我的可以滑动啊。可能是简书App的问题吧。
        刘建国大好人: @行走的少年郎 代码那个地方不是应该能左右滑动吗,我滑不了
        ITCharge:@刘建国大好人 抱歉,你问的问题太过模糊,我不太能够理解。建议百度或谷歌搜索一下问题的解决方法。谢谢。

      本文标题:iOS多线程:『GCD』详尽总结

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