iOS多线程:GCD

1. GCD简介

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

为何要用GCD呢? 由于 GCD 有不少好处啊,具体以下:编程

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

2. GCD任务和队列

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

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

同步执行(sync)多线程

  1. 同步添加任务到指定的队列中,在添加的任务执行结束以前,会一直等待,直到队列里面的任务完成以后再继续执行。
  2. 只能在当前线程中执行任务,不具有开启新线程的能力。

异步执行(async)并发

  1. 异步添加任务到指定的队列中,它不会作任何等待,能够继续执行任务。
  2. 能够在新的线程中执行任务,具有开启新线程的能力。

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

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

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

在这里插入图片描述
在 GCD 中有两种队列: 串行队列并发队列。二者都符合 FIFO(先进先出)的原则。二者的主要区别是: 执行顺序不一样,以及开启线程数不一样。

串行队列(Serial Dispatch Queue)

每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

并发队列(Concurrent Dispatch Queue)

可让多个任务并发(同时)执行。(能够开启多个线程,而且同时执行任务)

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

3. GCD 的使用步骤

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

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

  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);
复制代码
  1. 对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue) (1) 全部放在主队列中的任务,都会放到主线程中执行 (2) 可以使用dispatch_get_main_queue()得到主队列
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
复制代码
  1. 对于并发队列,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. 异步执行 + 主队列

那么这几种不一样组合方式各有什么区别呢

区别 并发队列 串行队列 主队列
同步 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
异步 有开启新线程,并发执行任务 有开启新线程(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) {
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    dispatch_sync(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    
    NSLog(@"syncConcurrent---end");
}
复制代码

输出结果: 2019-02-15 11:15:46.343931+0800 队列及同步异步[2833:84300] currentThread---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344092+0800 队列及同步异步[2833:84300] syncConcurrent---begin 2019-02-15 11:15:46.344227+0800 队列及同步异步[2833:84300] 1---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344329+0800 队列及同步异步[2833:84300] 1---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344453+0800 队列及同步异步[2833:84300] 2---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344554+0800 队列及同步异步[2833:84300] 2---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344647+0800 队列及同步异步[2833:84300] 3---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344740+0800 队列及同步异步[2833:84300] 3---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344838+0800 队列及同步异步[2833:84300] syncConcurrent---end

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

  1. 全部的任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行 不具有开启新线程的能力)
  2. 同步任务须要等待队列的任务执行结束
  3. 任务按顺序执行的。按顺序执行的缘由:虽然并发队列能够开启多个线程,而且同时执行多个任务。可是由于自己不能建立新线程,只有当前线程这一个线程(同步任务不具有开启新线程的能力),因此也就不存在并发。并且当前线程只有等待当前队列中正在执行的任务执行完毕以后,才能继续接着执行下面的操做(同步任务须要等待队列的任务执行结束)。因此任务只能一个接一个按顺序执行,不能同时被执行

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");
}
复制代码

输出结果 2019-02-15 14:30:29.722104+0800 队列及同步异步[7592:229106] currentThread---<NSThread: 0x6000005ce900>{number = 1, name = main} 2019-02-15 14:30:29.722276+0800 队列及同步异步[7592:229106] asyncConcurrent---begin 2019-02-15 14:30:29.722390+0800 队列及同步异步[7592:229106] asyncConcurrent---end 2019-02-15 14:30:31.723953+0800 队列及同步异步[7592:229185] 3---<NSThread: 0x6000005aa500>{number = 5, name = (null)} 2019-02-15 14:30:31.723953+0800 队列及同步异步[7592:229188] 1---<NSThread: 0x6000005aae40>{number = 3, name = (null)} 2019-02-15 14:30:31.723970+0800 队列及同步异步[7592:229187] 2---<NSThread: 0x6000005af700>{number = 4, name = (null)} 2019-02-15 14:30:33.724463+0800 队列及同步异步[7592:229187] 2---<NSThread: 0x6000005af700>{number = 4, name = (null)} 2019-02-15 14:30:33.724463+0800 队列及同步异步[7592:229185] 3---<NSThread: 0x6000005aa500>{number = 5, name = (null)} 2019-02-15 14:30:33.724535+0800 队列及同步异步[7592:229188] 1---<NSThread: 0x6000005aae40>{number = 3, name = (null)}

异步执行 + 并发队列中能够看出:

  1. 除了当前线程(主线程),系统又开启了3个线程,而且任务是交替/同时执行的。(异步执行具有开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。
  2. 全部任务是在打印的syncConcurrent---begin和syncConcurrent---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");
}
复制代码

输出结果 2019-02-15 14:33:40.462668+0800 队列及同步异步[7679:232473] currentThread---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:40.462831+0800 队列及同步异步[7679:232473] syncSerial---begin 2019-02-15 14:33:42.464177+0800 队列及同步异步[7679:232473] 1---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:44.465682+0800 队列及同步异步[7679:232473] 1---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:46.466694+0800 队列及同步异步[7679:232473] 2---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:48.467783+0800 队列及同步异步[7679:232473] 2---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:50.468335+0800 队列及同步异步[7679:232473] 3---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:52.469841+0800 队列及同步异步[7679:232473] 3---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:52.470107+0800 队列及同步异步[7679:232473] syncSerial---end

同步执行 + 串行队列能够看到:

  1. 全部任务都是在当前线程(主线程)中执行的,并无开启新的线程(同步执行不具有开启新线程的能力)。
  2. 全部任务都在打印的syncConcurrent---begin和syncConcurrent---end之间执行(同步任务须要等待队列的任务执行结束)。
  3. 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)

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");
}
复制代码

输出结果 2019-02-15 14:35:51.554014+0800 队列及同步异步[7746:234367] currentThread---<NSThread: 0x60000305e940>{number = 1, name = main} 2019-02-15 14:35:51.554171+0800 队列及同步异步[7746:234367] asyncSerial---begin 2019-02-15 14:35:51.554302+0800 队列及同步异步[7746:234367] asyncSerial---end 2019-02-15 14:35:53.555205+0800 队列及同步异步[7746:234408] 1---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:35:55.556721+0800 队列及同步异步[7746:234408] 1---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:35:57.560216+0800 队列及同步异步[7746:234408] 2---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:35:59.561222+0800 队列及同步异步[7746:234408] 2---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:36:01.566033+0800 队列及同步异步[7746:234408] 3---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:36:03.570689+0800 队列及同步异步[7746:234408] 3---<NSThread: 0x60000303cd00>{number = 3, name = (null)}

异步执行 + 串行队列能够看到:

  1. 开启了一条新线程(异步执行具有开启新线程的能力,串行队列只开启一个线程)
  2. 全部任务都是在打印的syncConcurrent---begin和syncConcurrent---end以后才开始执行的(异步执行不会作任何等待,能够继续执行任务)
  3. 任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行

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

主队列:GCD自带的一种特殊的串行队列

  1. 全部放在主队列中的任务,都会放到主线程中执行
  2. 可以使用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");
}
复制代码

输出结果: 2019-02-15 14:42:25.410352+0800 队列及同步异步[7914:239431] currentThread---<NSThread: 0x6000014f5400>{number = 1, name = main} 2019-02-15 14:42:25.410503+0800 队列及同步异步[7914:239431] syncMain---begin

同步执行 + 主队列能够惊奇的发现:

在主线程中使用同步执行 + 主队列,追加到主线程的任务一、任务二、任务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];
复制代码

输出结果 2019-02-15 14:44:06.075954+0800 队列及同步异步[7968:241022] currentThread---<NSThread: 0x6000008cb080>{number = 3, name = (null)} 2019-02-15 14:44:06.076113+0800 队列及同步异步[7968:241022] syncMain---begin 2019-02-15 14:44:08.090721+0800 队列及同步异步[7968:240949] 1---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:10.092199+0800 队列及同步异步[7968:240949] 1---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:12.095315+0800 队列及同步异步[7968:240949] 2---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:14.096453+0800 队列及同步异步[7968:240949] 2---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:16.099389+0800 队列及同步异步[7968:240949] 3---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:18.099756+0800 队列及同步异步[7968:240949] 3---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:18.100123+0800 队列及同步异步[7968:241022] syncMain---end

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

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

为何如今就不会卡住了呢? 由于syncMain 任务放到了其余线程里,而任务一、任务二、任务3都在追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其余线程中执行到追加任务1到主队列中,由于主队列如今没有正在执行的任务,因此,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务二、任务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");
}
复制代码

输出结果: 2019-02-15 14:47:49.136074+0800 队列及同步异步[8068:243847] currentThread---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:49.136221+0800 队列及同步异步[8068:243847] asyncMain---begin 2019-02-15 14:47:49.136320+0800 队列及同步异步[8068:243847] asyncMain---end 2019-02-15 14:47:51.153197+0800 队列及同步异步[8068:243847] 1---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:53.153685+0800 队列及同步异步[8068:243847] 1---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:55.155159+0800 队列及同步异步[8068:243847] 2---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:57.156706+0800 队列及同步异步[8068:243847] 2---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:59.158292+0800 队列及同步异步[8068:243847] 3---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:48:01.159829+0800 队列及同步异步[8068:243847] 3---<NSThread: 0x600002c65400>{number = 1, name = main}

异步执行 + 主队列能够看到:

  1. 全部任务都是在当前线程(主线程)中执行的,并无开启新的线程(虽然异步执行具有开启线程的能力,但由于是主队列,因此全部任务都在主线程中)。
  2. 全部任务是在打印的syncConcurrent---begin和syncConcurrent---end以后才开始执行的(异步执行不会作任何等待,能够继续执行任务)。
  3. 任务是按顺序执行的(由于主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行

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]);      // 打印当前线程
        });
    });
}
复制代码

输出结果 2019-02-15 14:51:38.214783+0800 队列及同步异步[8174:247133] 1---<NSThread: 0x600001de02c0>{number = 3, name = (null)} 2019-02-15 14:51:40.220222+0800 队列及同步异步[8174:247133] 1---<NSThread: 0x600001de02c0>{number = 3, name = (null)} 2019-02-15 14:51:42.221872+0800 队列及同步异步[8174:247009] 2---<NSThread: 0x600001d8d300>{number = 1, name = main}

能够看到在其余线程中先执行任务,执行完了以后回到主线程执行主线程的相应操做。

6. GCD的其余方法

6.1 GCD 栅栏方法:dispatch_barrier_async

咱们有时候须要异步执行两组操做,并且第一组操做执行完毕以后,才能开始执行第二组操做,这样咱们就须要一个栅栏同样的一个方法将两组异步执行的操做组给分隔起来,固然这里的操做组里能够包含一个或者多个任务,这就须要用到dispatch_barrier_async方法在两个操做组间造成栅栏。 dispatch_barrier_async函数会等待前边追加到并发队列中的任务所有执行完毕以后,再将指定的任务追加到该异步队列中。而后在dispatch_barrier_async函数追加的任务执行完毕以后,异步队列才恢复为通常动做,接着追加任务到该异步队列并开始执行。具体以下图所示:

在这里插入图片描述

/**
 * 栅栏方法 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]);      // 打印当前线程
        }
    });
}
复制代码

输出结果 2019-02-15 15:35:08.097053+0800 队列及同步异步[9256:283315] 2---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:08.097050+0800 队列及同步异步[9256:283316] 1---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:10.099652+0800 队列及同步异步[9256:283315] 2---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:10.099652+0800 队列及同步异步[9256:283316] 1---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:12.100542+0800 队列及同步异步[9256:283315] barrier---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:14.106086+0800 队列及同步异步[9256:283315] barrier---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:16.111681+0800 队列及同步异步[9256:283316] 4---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:16.111683+0800 队列及同步异步[9256:283315] 3---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:18.116375+0800 队列及同步异步[9256:283316] 4---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:18.116375+0800 队列及同步异步[9256:283315] 3---<NSThread: 0x600001ff1e40>{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]);  // 打印当前线程
    });
}
复制代码

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_apply。dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待所有队列执行结束

咱们能够利用异步队列同时遍历。好比说遍历 0~5 这6个数字,for 循环的作法是每次取出一个元素,逐个遍历。dispatch_apply能够同时遍历多个数字。

/**
 * 快速迭代方法 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");
}
复制代码

输出结果 2019-02-15 15:39:01.005273+0800 队列及同步异步[9366:286604] apply---begin 2019-02-15 15:39:01.006663+0800 队列及同步异步[9366:286652] 3---<NSThread: 0x60000022c500>{number = 5, name = (null)} 2019-02-15 15:39:01.006663+0800 队列及同步异步[9366:286655] 1---<NSThread: 0x600000203840>{number = 3, name = (null)} 2019-02-15 15:39:01.006663+0800 队列及同步异步[9366:286651] 2---<NSThread: 0x60000022ca00>{number = 4, name = (null)} 2019-02-15 15:39:01.006663+0800 队列及同步异步[9366:286604] 0---<NSThread: 0x600000249400>{number = 1, name = main} 2019-02-15 15:39:01.006859+0800 队列及同步异步[9366:286652] 5---<NSThread: 0x60000022c500>{number = 5, name = (null)} 2019-02-15 15:39:01.006869+0800 队列及同步异步[9366:286651] 4---<NSThread: 0x60000022ca00>{number = 4, name = (null)} 2019-02-15 15:39:01.006979+0800 队列及同步异步[9366:286604] apply---end

从dispatch_apply相关代码执行结果能够看出:

  1. 0~5打印顺序不定,最后打印了apply--end

由于是在并发队列中异步执行任务,因此各个任务的执行时间长短不定,最后结束顺序也不定,可是apply---end必定在最后执行。这是由于dispatch_apply函数会等待所有任务执行完毕

6.5 GCD 的队列组:dispatch_group

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

  1. 调用队列组的 dispatch_group_async 先把任务放到队列中,而后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现dispatch_group_async。
  2. 调用队列组的 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(), ^{
        // 等前面的异步任务一、任务2都执行完毕后,回到主线程执行下边任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操做
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}
复制代码

输出结果: 2019-02-15 15:44:48.852840+0800 队列及同步异步[9515:291211] currentThread---<NSThread: 0x600003ff4040>{number = 1, name = main} 2019-02-15 15:44:48.852983+0800 队列及同步异步[9515:291211] group---begin 2019-02-15 15:44:50.854857+0800 队列及同步异步[9515:291265] 2---<NSThread: 0x600003fbb000>{number = 4, name = (null)} 2019-02-15 15:44:50.854867+0800 队列及同步异步[9515:291262] 1---<NSThread: 0x600003f90dc0>{number = 3, name = (null)} 2019-02-15 15:44:52.860365+0800 队列及同步异步[9515:291265] 2---<NSThread: 0x600003fbb000>{number = 4, name = (null)} 2019-02-15 15:44:52.860383+0800 队列及同步异步[9515:291262] 1---<NSThread: 0x600003f90dc0>{number = 3, name = (null)} 2019-02-15 15:44:54.862032+0800 队列及同步异步[9515:291211] 3---<NSThread: 0x600003ff4040>{number = 1, name = main} 2019-02-15 15:44:56.863549+0800 队列及同步异步[9515:291211] 3---<NSThread: 0x600003ff4040>{number = 1, name = main} 2019-02-15 15:44:56.863819+0800 队列及同步异步[9515:291211] 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");
}
复制代码

输出: 2019-02-15 15:47:24.820145+0800 队列及同步异步[9593:293510] currentThread---<NSThread: 0x600000306940>{number = 1, name = main} 2019-02-15 15:47:24.820309+0800 队列及同步异步[9593:293510] group---begin 2019-02-15 15:47:26.824388+0800 队列及同步异步[9593:293548] 2---<NSThread: 0x6000003622c0>{number = 3, name = (null)} 2019-02-15 15:47:26.824388+0800 队列及同步异步[9593:293547] 1---<NSThread: 0x60000035dcc0>{number = 4, name = (null)} 2019-02-15 15:47:28.826498+0800 队列及同步异步[9593:293547] 1---<NSThread: 0x60000035dcc0>{number = 4, name = (null)} 2019-02-15 15:47:28.826498+0800 队列及同步异步[9593:293548] 2---<NSThread: 0x6000003622c0>{number = 3, name = (null)} 2019-02-15 15:47:28.826852+0800 队列及同步异步[9593:293510] group---end

从dispatch_group_wait相关代码运行输出结果能够看出: 当全部任务执行完成以后,才执行 dispatch_group_wait 以后的操做。可是,使用dispatch_group_wait 会阻塞当前线程

dispatch_group_enter、dispatch_group_leave

  1. dispatch_group_enter 标志着一个任务追加到 group,执行一次,至关于 group 中未执行完毕任务数+1
  2. dispatch_group_leave 标志着一个任务离开了 group,执行一次,至关于 group 中未执行完毕任务数-1。
  3. 当 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");
}
复制代码

输出结果: 2019-02-15 15:53:00.944543+0800 队列及同步异步[9750:298146] currentThread---<NSThread: 0x6000011e2900>{number = 1, name = main} 2019-02-15 15:53:00.944726+0800 队列及同步异步[9750:298146] group---begin 2019-02-15 15:53:02.949542+0800 队列及同步异步[9750:298194] 1---<NSThread: 0x6000011862c0>{number = 3, name = (null)} 2019-02-15 15:53:02.949542+0800 队列及同步异步[9750:298197] 2---<NSThread: 0x600001186300>{number = 4, name = (null)} 2019-02-15 15:53:04.950626+0800 队列及同步异步[9750:298194] 1---<NSThread: 0x6000011862c0>{number = 3, name = (null)} 2019-02-15 15:53:04.950653+0800 队列及同步异步[9750:298197] 2---<NSThread: 0x600001186300>{number = 4, name = (null)} 2019-02-15 15:53:06.952269+0800 队列及同步异步[9750:298146] 3---<NSThread: 0x6000011e2900>{number = 1, name = main} 2019-02-15 15:53:08.953768+0800 队列及同步异步[9750:298146] 3---<NSThread: 0x6000011e2900>{number = 1, name = main} 2019-02-15 15:53:08.954036+0800 队列及同步异步[9750:298146] 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 提供了三个函数。

  1. dispatch_semaphore_create:建立一个Semaphore并初始化信号的总量
  2. dispatch_semaphore_signal:发送一个信号,让信号总量加1
  3. dispatch_semaphore_wait:可使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),不然就能够正常执行。

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

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

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

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以后,总信号量加1,此时 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张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另外一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止

非线程安全(不使用 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 机制以后,获得的票数是正确的,没有出现混乱的状况。咱们也就解决了多个线程同步的问题。

相关文章
相关标签/搜索