iOS 多线程(GCD)的使用

1: GCD简介

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

引自百度百科:
Grand Central Dispatch(GCD): 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其余对称多处理系统。 它是一个在线程池模式的基础上执行的并发任务。程序员

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

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

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

2: GCD任务和队列

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

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

  • 同步执行(sync):多线程

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

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

举个简单的例子: 你要打电话给小明和小白。app

同步执行就是,你打电话给小明的时候,不能同时打给小白,等到给小明打完了,才能打给小白(等待任务执行结束)。 并且只能用当前的电话(不具有开启新线程的能力)。异步

而异步执行就是, 你打电话给小明的时候,不等和小明通话结束,还能直接给小白打电话,不用等着和小明通话结束再打(不用等待任务执行结束)。除了当前电话,你还可使用其余所能使用的电话(具有开启新线程的能力)。

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

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

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

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

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

二者具体区别以下两图所示:

                 串行队列

                 并发队列

3: GCD的使用步骤

GCD的使用步骤其实很简单, 只有两步:
  1:建立一个队列(串行队列或并发队列)
  2: 将任务追加到任务的等待队列中,而后系统就会根据任务类型执行任务(同步执行或异步执行)
下边来看看队列的建立方法/获取方法, 以及任务的建立方法。

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

  • 可使用dispatch_queue_create来建立队列, 须要传入两个参数, 第一个参数表示队列的惟一标识符, 用于DEUBG,可为空, 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 queue1 = 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:异步执行 + 串行队列
复制代码

实际上,刚才还说了两种特殊队列: 全局并发队列、主队列。 全局并发队列能够做为普通并发队列来使用。可是主队列由于有点特殊, 因此咱们就又多了两种组合方式。 这样就有六种不一样的组合方式了。

5: 同步执行 + 主队列
6:异步执行 + 主队列
复制代码

那么这几种不一样组合方式各有什么区别呢?这里为了方便, 先上结果, 再来说解。

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

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");
}

输出:
currentThread = <NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent---begin
1---<NSThread: 0x600003aca940>{number = 1, name = main}
1---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent -- end
复制代码

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

  • 全部任务都是在当前线程(主线程)中执行的,没有开启新的线程(同步执行不具有开启新线程的能力)
  • 全部任务都在打印的syncConcurrent---begin和syncConcurrent -- 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");
}

输出结果:
currentThread = <NSThread: 0x600000f494c0>{number = 1, name = main}
asyncConcurrent---begin
asyncConcurrent -- end
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
复制代码

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

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

输出结果:
currentThread = <NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial---begin
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial -- end
复制代码

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

  • 全部任务都是在当前线程(主线程)中执行的, 并无开启新的线程(同步执行不具有开启新线程的能力)
  • 全部任务都在打印的syncSerial---begin和syncSerial -- 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");
}

输出结果:
currentThread = <NSThread: 0x60000357e8c0>{number = 1, name = main}
asyncSerial---begin
asyncSerial---end
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{number = 3, name = (null)}
复制代码

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

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

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

  • 主队列: 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");
}


输出结果:
currentThread = <NSThread: 0x6000031c28c0>{number = 1, name = main}
syncMain---begin
(lldb) 
复制代码

在同步执行 + 主队列中能够发现:

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

这是由于咱们在主线程中执行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];   

输出结果:  
currentThread = <NSThread: 0x600003fe09c0>{number = 3, name = (null)}
syncMain---begin
1---<NSThread: 0x600003f65480>{number = 1, name = main}
1---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
syncMain---end
复制代码

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

  • 全部任务都是在主线程(非当前线程)中执行的, 没有开启新的线程(全部放在主队列中的任务, 都会放到主线程中执行)
  • 全部任务都在打印的syncMain---begin和syncMain---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");
}


输出结果:
currentThread = <NSThread: 0x6000017dd4c0>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
复制代码

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

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

5: GCD 线程间的通讯

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

/**
 * 线程间通讯
 */
- (void)communication {
    NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
    //获取全局并发队列
    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:4];          //模拟耗时操做
        NSLog(@"2---%@",NSThread.currentThread);    //打印当前线程
    });
}

输出结果:
currentThread---<NSThread: 0x60000137c980>{number = 1, name = main}
不符合标砖
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
2---<NSThread: 0x60000137c980>{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 {
    NSLog(@"currentThread---%@",NSThread.currentThread);//打印当前线程
    
    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);
        }
    });
}   

输出结果:
currentThread---<NSThread: 0x6000002ec080>{number = 1, name = main}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, 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(@"after---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);    //打印当前线程
    });
}

输出结果:   
17:51:02.998177+0800 currentThread---<NSThread: 0x600002cacd40>{number = 1, name = main}
17:51:02.998384+0800 after---begin
17:51:04.998680+0800 after---<NSThread: 0x600002cacd40>{number = 1, name = main}
复制代码

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

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

  • 咱们在建立单例、或者有整个程序运行过程当中只执行一次的代码时,咱们就用到了GCD的dispatch_once函数.使用dispatch_once函数能保证某段代码在程序运行过程当中只执行1次,而且即便在多线程的环境下,dispatch_once也能够保证线程安全.
/**
 * 一次性代码(只执行一次): dispatch_once
 */
- (void)once {
    for (int i = 0; i < 10; i++) {
        NSLog(@"第%d次执行GCD一次性代码",i);        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            //只执行1次的代码(这里面默认是线程安全的)
            NSLog(@"只执行1次的代码");
        });
    }
}

输出结果:
第0次执行GCD一次性代码
只执行1次的代码
第1次执行GCD一次性代码
第2次执行GCD一次性代码
第3次执行GCD一次性代码
第4次执行GCD一次性代码
第5次执行GCD一次性代码
第6次执行GCD一次性代码
第7次执行GCD一次性代码
第8次执行GCD一次性代码
第9次执行GCD一次性代码
复制代码

从输出结果能够看出, 不管执行多少次dispatch_once,最终都只会执行一次;

6.4 GCD快速迭代方法:dispatch_apply

  • 一般咱们会用for循环遍历,可是GCD给咱们提供了快速迭代的函数dispatch_apply. dispatch_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");
}

输出结果:
apply---begin
2---<NSThread: 0x60000198ccc0>{number = 5, name = (null)}
4---<NSThread: 0x600001981000>{number = 6, name = (null)}
3---<NSThread: 0x6000019f14c0>{number = 1, name = main}
0---<NSThread: 0x6000019ab380>{number = 3, name = (null)}
1---<NSThread: 0x60000198a080>{number = 4, name = (null)}
5---<NSThread: 0x6000019ab4c0>{number = 7, name = (null)}
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");
    });
}

输出结果:
currentThread---<NSThread: 0x60000202e8c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
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");
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];         //模拟耗时操做
            NSLog(@"3---%@",NSThread.currentThread);   //打印当前线程
        }
    });

    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 追加任务4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];         //模拟耗时操做
            NSLog(@"4---%@",NSThread.currentThread);   //打印当前线程
        }
    });
}

输出结果:
currentThread---<NSThread: 0x6000018e94c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
group---end

4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
复制代码

从dispatch_group_wait相关代码运行输出结果能够看出:

  • 当dispatch_group_wait以前全部的任务都执行完成以后, 才会执行dispatch_group_wait以后的操做.
  • 使用dispatch_group_wait会阻塞当前线程(从输出结果就能够看出)
  • dispatch_group_wait的功能相似于栅栏方法dispatch_barrier_async, 不管有几个并发队列, 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");
    });
}

输出结果:
currentThread---<NSThread: 0x600000326c80>{number = 1, name = main}
group---begin
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
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时等待, 不可经过.计数为0或大于0时, 计数减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);//semaphore初始建立时计数为0
    
    __block int number = 0;
    dispatch_async(queue, ^{
       // 追加任务1
        [NSThread sleepForTimeInterval:2];                     //模拟耗时操做
        NSLog(@"1---%@",NSThread.currentThread);
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);//总信号量+1, 此时semaphore == 0, 正在被阻塞的线程(主线程)恢复继续执行
    });

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);  //semaphore减1, 此时semaphore == -1, 当前线程进入等待状态
    NSLog(@"semaphore---end, number = %zd",(long)number);
}

输出结果:
currentThread---<NSThread: 0x600001d394c0>{number = 1, name = main}
semaphore---begin
1---<NSThread: 0x600001d46480>{number = 3, name = (null)}
semaphore---end, number = 100
复制代码

从Dispatch Semaphore实现线程同步的代码能够看到:

  • semaphore---end 是在执行完 number = 100以后才打印的. 并且输出结果number为100, 这是由于异步执行不会作任何等待, 能够继续执行任务.
    执行顺序以下:
    • semaphore 初始建立时计数为0.
    • 异步执行将任务1追加到队列以后, 不作等待, 接着执行dispatch_semaphore_wait方法, semaphore减1, 此时semaphore == -1, 当前线程进入等待状态.
    • 而后, 异步任务1开始执行, 任务1执行到dispatch_semaphore_signal以后, 总信号量加1, 此时semaphore == 0, 正在被阻塞的线程(主线程)恢复继续执行.
    • 最后打印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(@"剩余票数: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
            [NSThread sleepForTimeInterval:0.2];
        }
        else { //若是已卖完, 关闭售票窗口
            NSLog(@"全部火车票均已售完");
            break;
        }
    }
}

输出结果:   
currentThread---<NSThread: 0x60000083cf80>{number = 1, name = main}
semaphore--begin
剩余票数: 48 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 49 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 46 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 47 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 45 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
...
剩余票数: 5 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 4 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 3 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩余票数: 1 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩余票数: 2 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火车票均已售完
剩余票数: 0 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火车票均已售完
复制代码

能够看到在不考虑线程安全, 不适用semaphore的状况下, 获得的票数是错乱的, 这样显然不符合咱们的需求, 因此咱们须要考虑线程安全的问题。

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

考虑线程安全的代码:

/**
 * 线程安全: 使用 semaphore 加锁
 * 初始化火车票数量, 卖票窗口(线程安全), 并开始卖票
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",NSThread.currentThread);          //打印当前线程
    NSLog(@"semaphore--begin");
    
    self.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 saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 售卖火车票(线程安全)
 */
- (void)saleTicketNotSafe {
    while (1) {
        //至关于加锁
        dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) {  //若是还有票, 继续售票
            self.ticketSurplusCount--;
            NSLog(@"剩余票数: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
            [NSThread sleepForTimeInterval:0.2];
        }
        else { //若是已卖完, 关闭售票窗口
            NSLog(@"全部火车票均已售完");
            //至关于解锁
            dispatch_semaphore_signal(self.semaphoreLock);
            break;
        }
        
        //至关于解锁
        dispatch_semaphore_signal(self.semaphoreLock);
    }
}

输出结果:
currentThread---<NSThread: 0x60000317e8c0>{number = 1, name = main}
semaphore--begin
剩余票数: 49 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 48 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 47 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 46 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 45 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
...
剩余票数: 5 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 4 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 3 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 2 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩余票数: 1 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩余票数: 0 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
全部火车票均已售完
全部火车票均已售完
复制代码

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

原文: www.cnblogs.com/yxl-151217/…

相关文章
相关标签/搜索