iOS多线程GCD使用方式

一、基本介绍

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

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。

GCD中的一个任务可被用于创造一个被放置于队列的工作项目或者事件源。如果一个任务被分配到一个事件源,那么一个由功能或者程序块组成的工作单元会被放置于一个适当的队列中。苹果公司认为GCD相比于普通的一个接一个的执行任务的方式更为有效率。

使用GCD 有以下好处:
  • GCD可用于多核的并行运算;
  • GCD会自动利用更多的 CPU 内核(比如双核、四核);
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码,因为线程是安全的。

二、使用方式

GCD的使用需要任务和队列的相互配合:

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

  • 同步执行(sync):同步添加任务到指定的队列中,该任务会在等待指定队列中其他任务执行完成后再执行。而添加的任务只能在当前线程中执行任务,不具备开启新线程的能力。
    同步执行创建方式:
//同步执行
dispatch_sync(queue, ^{
   //添加执行任务
});
  • 异步执行(async):异步添加任务到指定的队列中,该任务不会等待队列中其他任务执行完成,直接开始执行。而添加的任务可以在新的线程中执行任务,具备开启新线程的能力(开启一个还是多个线程需要根据执行队列来确定)。
    异步执行创建方式:
//异步执行
dispatch_async(queue, ^{
   //添加执行任务
});

其中的queue就是我们所需要创建的执行队列。

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在 GCD 中有两种队列:串行队列并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue):只开启一个线程,在线程中任务是按顺序的一个接一个的执行。
    串行队列创建方式:
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
//创建主队列
dispatch_queue_t queue = dispatch_get_main_queue();

其中主队列是GCD提供的一种特殊的串行队列。

  • 并发队列(Concurrent Dispatch Queue):根据任务执行的方式(同步执行或异步执行)来确定开启一个或者多个线程。同步执行时是在开启的一个线程中按顺序同步执行。异步执行时是在开启的多个线程中并发(同时)执行任务。
    并行队列创建方式:
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
//创建全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

其中全局并发队列是GCD提供的一种特殊的并发队列。

下面介绍一下GCD任务和队列的组合使用方式:

1、同步执行 + 并发队列
  • 同步执行不具备开启新线程的能力,因此所有任务都是在当前线程(主线程)中执行的。
  • 虽然并发队列可以开启多个线程并且可以同时执行多个任务,但是因为同步执行任务不具备开启新线程的能力,因此只有当前线程这一个线程,也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
//1.同步执行 + 并发队列
- (instancetype)initSyncAndConcurrentDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"SyncAndConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);//创建并发队列
        dispatch_sync(queue, ^{//同步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_sync(queue, ^{//同步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_sync(queue, ^{//同步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        NSLog(@"SyncAndConcurrent---end");
    }
    return self;
}

输出结果:

2018-07-18 14:06:25.808498+0800 GCD[3487:140302] currentThread---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:25.808716+0800 GCD[3487:140302] SyncAndConcurrent---begin
2018-07-18 14:06:27.810127+0800 GCD[3487:140302] 1---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:29.811518+0800 GCD[3487:140302] 1---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:31.812374+0800 GCD[3487:140302] 2---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:33.812720+0800 GCD[3487:140302] 2---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:35.813879+0800 GCD[3487:140302] 3---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:37.814369+0800 GCD[3487:140302] 3---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:06:37.814666+0800 GCD[3487:140302] SyncAndConcurrent---end
2、异步执行 + 并发队列
  • 异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务。因此除了当前线程(主线程),系统又开启了多个线程,并且任务是同时执行的。
  • 因为是并发队列,所以当前线程中所有任务没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。
//2.异步执行 + 并发队列
- (instancetype)initAsyncAndConcurrentDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"AsyncAndConcurrent---begin");
        
        dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);//创建并发队列
        dispatch_async(queue, ^{//异步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{//异步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{//异步执行
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        NSLog(@"AsyncAndConcurrent---end");
    }
    return self;
}

输出结果:

2018-07-18 14:13:36.852829+0800 GCD[3487:140302] currentThread---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:13:36.853047+0800 GCD[3487:140302] AsyncAndConcurrent---begin
2018-07-18 14:13:36.853189+0800 GCD[3487:140302] AsyncAndConcurrent---end
2018-07-18 14:13:38.854025+0800 GCD[3487:140559] 2---<NSThread: 0x60400027cc40>{number = 4, name = (null)}
2018-07-18 14:13:38.854027+0800 GCD[3487:145869] 1---<NSThread: 0x600000462780>{number = 3, name = (null)}
2018-07-18 14:13:38.854025+0800 GCD[3487:145877] 3---<NSThread: 0x604000277c00>{number = 5, name = (null)}
2018-07-18 14:13:40.858188+0800 GCD[3487:145869] 1---<NSThread: 0x600000462780>{number = 3, name = (null)}
2018-07-18 14:13:40.858188+0800 GCD[3487:140559] 2---<NSThread: 0x60400027cc40>{number = 4, name = (null)}
2018-07-18 14:13:40.858288+0800 GCD[3487:145877] 3---<NSThread: 0x604000277c00>{number = 5, name = (null)}
3、同步执行 + 串行队列
  • 同步执行不具备开启新线程的能力,因此所有任务都是在当前线程(主线程)中执行的。
  • 因为是串行队列,所以任务是一个接一个按顺序执行,并且每次只有一个任务被执行。
//3.同步执行 + 串行队列
- (instancetype)initSyncAndSerialDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"SyncAndSerial---begin");
        
        dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);//创建串行队列
        dispatch_sync(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];           //模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);   //打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];           //模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);   //打印当前线程
            }
        });
        dispatch_sync(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];           //模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);   //打印当前线程
            }
        });
        NSLog(@"SyncAndSerial---end");
    }
    return self;
}

输出结果:

2018-07-18 14:18:52.586924+0800 GCD[3487:140302] currentThread---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:18:52.587135+0800 GCD[3487:140302] SyncAndSerial---begin
2018-07-18 14:18:54.587815+0800 GCD[3487:140302] 1---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:18:56.588782+0800 GCD[3487:140302] 1---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:18:58.590248+0800 GCD[3487:140302] 2---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:19:00.591183+0800 GCD[3487:140302] 2---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:19:02.591603+0800 GCD[3487:140302] 3---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:19:04.593199+0800 GCD[3487:140302] 3---<NSThread: 0x604000065440>{number = 1, name = main}
2018-07-18 14:19:04.593466+0800 GCD[3487:140302] SyncAndSerial---end
4、异步执行 + 串行队列
  • 异步执行具备开启新线程的能力。但由于是串行队列,只能开启一个线程。因此除了当前线程(主线程),系统又开启了一个线程。
  • 因为是串行队列,所以任务是一个接一个按顺序执行,并且每次只有一个任务被执行。但串行队列中的所有任务都是在新开启的线程中执行的。
//4.异步执行 + 串行队列
- (instancetype)initAsyncAndSerialDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"AsyncAndSerial---begin");
        
        dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);//创建串行队列
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        NSLog(@"AsyncAndSerial---end");
    }
    return self;
}

输出结果:

2018-07-18 14:25:13.368635+0800 GCD[3787:154025] currentThread---<NSThread: 0x604000066400>{number = 1, name = main}
2018-07-18 14:25:13.368890+0800 GCD[3787:154025] AsyncAndSerial---begin
2018-07-18 14:25:13.369052+0800 GCD[3787:154025] AsyncAndSerial---end
2018-07-18 14:25:15.373476+0800 GCD[3787:154089] 1---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
2018-07-18 14:25:17.377079+0800 GCD[3787:154089] 1---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
2018-07-18 14:25:19.381357+0800 GCD[3787:154089] 2---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
2018-07-18 14:25:21.382271+0800 GCD[3787:154089] 2---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
2018-07-18 14:25:23.384662+0800 GCD[3787:154089] 3---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
2018-07-18 14:25:25.386662+0800 GCD[3787:154089] 3---<NSThread: 0x6000002709c0>{number = 3, name = (null)}
5、同步执行 + 主队列(会造成死锁)
  • 在主线程中使用同步执行 + 主队列会造成死锁
//5.同步执行 + 主队列(会造成死锁)
- (instancetype)initSyncAndMainDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"SyncAndMain---begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        NSLog(@"SyncAndMain---end");
    }
    return self;
}

输出结果:

2018-07-18 14:31:31.002501+0800 GCD[3787:154025] currentThread---<NSThread: 0x604000066400>{number = 1, name = main}
2018-07-18 14:31:31.002743+0800 GCD[3787:154025] SyncAndMain---begin

由此可以看出:

  • 在主线程中使用同步执行 + 主队列,追加到主线程的任务1不再执行了,而且syncMain---end也没有打印
  • 死锁原因:这是因为我们在主线程中添加同步操作时,同步操作要等主线程的任务执行完成才开始执行,此时同步操作处于等待状态。而主线程任务中含有已经添加的同步操作,而主线程只有等同步操作执行完成才能继续操作,而此时的同步操作处于等待状态,所以主线程会等待同步操作执行完成才继续执行,从而两者相互等待,以至于会造成线程死锁。
解决死锁的办法:把需要在主队列中同步执行的任务放到其他线程中去。
//同步执行 + 主队列(解决死锁调用方法)
- (instancetype)initSyncAndMainDispatchResolve {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"SyncAndMain---begin");
        //创建其他线程中调用同步执行 + 主队列
        [NSThread detachNewThreadSelector:@selector(SyncMain) toTarget:self withObject:nil];
    }
    return self;
}

- (void)SyncMain {
    NSLog(@"currentThread 4---%@",[NSThread currentThread]);//打印当前线程
    NSLog(@"SyncAndMain 4---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];            //模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];            //模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];            //模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
        }
    });
    NSLog(@"SyncAndMain---end");
}

输出结果:

2018-07-18 14:38:07.108703+0800 GCD[4000:163392] currentThread---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:07.108999+0800 GCD[4000:163392] SyncAndMain---begin
2018-07-18 14:38:07.110803+0800 GCD[4000:164570] currentThread 4---<NSThread: 0x600000468240>{number = 3, name = (null)}
2018-07-18 14:38:07.111920+0800 GCD[4000:164570] SyncAndMain 4---begin
2018-07-18 14:38:09.114080+0800 GCD[4000:163392] 1---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:11.115053+0800 GCD[4000:163392] 1---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:13.116707+0800 GCD[4000:163392] 2---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:15.117198+0800 GCD[4000:163392] 2---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:17.118623+0800 GCD[4000:163392] 2---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:19.120149+0800 GCD[4000:163392] 2---<NSThread: 0x60400006b600>{number = 1, name = main}
2018-07-18 14:38:19.120473+0800 GCD[4000:164570] SyncAndMain---end
6、异步执行 + 主队列
  • 虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都是在当前线程(主线程)中执行的,并没有开启新的线程。
  • 因为是串行队列,所以任务是一个接一个按顺序执行,并且每次只有一个任务被执行。
//6.异步执行 + 主队列
- (instancetype)initAsyncAndMainDispatch {
    if (self = [super init]) {
        NSLog(@"currentThread---%@",[NSThread currentThread]);//打印当前线程
        NSLog(@"AsyncAndMain---begin");
        
        dispatch_queue_t queue = dispatch_get_main_queue();   //获取主线程
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时线程
                NSLog(@"1---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时线程
                NSLog(@"2---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        dispatch_async(queue, ^{
            for (int i=0; i<2; i++) {
                [NSThread sleepForTimeInterval:2];            //模拟耗时线程
                NSLog(@"3---%@",[NSThread currentThread]);    //打印当前线程
            }
        });
        NSLog(@"AsyncAndMain---end");
    }
    return self;
}

输出结果:

2018-07-18 14:44:03.921354+0800 GCD[4125:168926] currentThread---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:03.921575+0800 GCD[4125:168926] AsyncAndMain---begin
2018-07-18 14:44:03.921763+0800 GCD[4125:168926] AsyncAndMain---end
2018-07-18 14:44:05.926029+0800 GCD[4125:168926] 1---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:07.928790+0800 GCD[4125:168926] 1---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:09.931443+0800 GCD[4125:168926] 2---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:11.933365+0800 GCD[4125:168926] 2---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:13.935818+0800 GCD[4125:168926] 3---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:44:15.937064+0800 GCD[4125:168926] 3---<NSThread: 0x60000006ce00>{number = 1, name = main}
7、同步执行 + 全局并发队列

由于全局并发队列等同于普通并发队列,所以该操作等同于同步执行 + 并发队列,请参考 1、同步执行 + 并发队列

8、异步执行 + 全局并发队列

由于全局并发队列等同于普通并发队列,所以该操作等同于异步执行 + 并发队列,请参考 2、异步执行 + 并发队列

9、GCD栅栏方法:dispatch_barrier_async
  • dispatch_barrier_async函数的作用是在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行。
  • 该函数需要配合并发队列(dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);)使用。
//9.GCD栅栏方法
- (void)dispatchBarrier {
    NSLog(@"currentThread --- %@",[NSThread currentThread]);
    NSLog(@"dispatchBarrier --- Begin");
    
    dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@",[NSThread currentThread]);
        }
    });
    //栅栏方法
    dispatch_barrier_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@",[NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4---%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i=0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"5---%@",[NSThread currentThread]);
        }
    });
    NSLog(@"dispatchBarrier --- end");
}

输出结果:

2018-07-18 14:59:21.577599+0800 GCD[4125:168926] currentThread --- <NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 14:59:21.577839+0800 GCD[4125:168926] dispatchBarrier --- Begin
2018-07-18 14:59:21.578039+0800 GCD[4125:168926] dispatchBarrier --- end
2018-07-18 14:59:23.581183+0800 GCD[4125:169172] 2---<NSThread: 0x604000273ec0>{number = 4, name = (null)}
2018-07-18 14:59:23.581191+0800 GCD[4125:180793] 1---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:25.585534+0800 GCD[4125:169172] 2---<NSThread: 0x604000273ec0>{number = 4, name = (null)}
2018-07-18 14:59:25.585605+0800 GCD[4125:180793] 1---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:27.589468+0800 GCD[4125:180793] 3---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:29.594449+0800 GCD[4125:180793] 3---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:31.599255+0800 GCD[4125:180793] 4---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:31.599255+0800 GCD[4125:169172] 5---<NSThread: 0x604000273ec0>{number = 4, name = (null)}
2018-07-18 14:59:33.603883+0800 GCD[4125:180793] 4---<NSThread: 0x600000477880>{number = 3, name = (null)}
2018-07-18 14:59:33.603883+0800 GCD[4125:169172] 5---<NSThread: 0x604000273ec0>{number = 4, name = (null)}
10、GCD延时执行方法:dispatch_after
  • dispatch_after函数可以实现在指定时间之后开始执行某个任务。需要注意的是dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。
//10.GCD延时执行方法
- (void)dispatchAfter {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  //打印当前线程
    NSLog(@"dispatchAfter---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@",[NSThread currentThread]);      //打印当前线程
    });
    NSLog(@"dispatchAfter---end");
}

输出结果:

2018-07-18 15:08:27.072470+0800 GCD[4125:168926] currentThread---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:08:27.072737+0800 GCD[4125:168926] dispatchAfter---begin
2018-07-18 15:08:27.072927+0800 GCD[4125:168926] dispatchAfter---end
2018-07-18 15:08:29.073087+0800 GCD[4125:168926] after---<NSThread: 0x60000006ce00>{number = 1, name = main}
11、GCD单例:dispatch_once
  • 使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行一次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。
#import "DispatchOnce.h"

static DispatchOnce * _onceManager;
@implementation DispatchOnce

//单例
+ (instancetype)dispatchOnceManager {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _onceManager = [[DispatchOnce alloc]init];
    });
    return _onceManager;
}

+ (instancetype)dispatchManager {
    _onceManager = [[DispatchOnce alloc]init];
    return _onceManager;
}

@end
//打印创建对象地址
DispatchOnce * one = [DispatchOnce dispatchOnceManager];
DispatchOnce * two = [DispatchOnce dispatchOnceManager];
DispatchOnce * three = [DispatchOnce dispatchManager];
DispatchOnce * four = [DispatchOnce dispatchManager];
NSLog(@"one  :%@",one);
NSLog(@"two  :%@",two);
NSLog(@"three:%@",three);
NSLog(@"four :%@",four);

输出结果:

2018-07-18 15:12:01.385630+0800 GCD[4125:168926] one  :<DispatchOnce: 0x60400001bfd0>
2018-07-18 15:12:01.385830+0800 GCD[4125:168926] two  :<DispatchOnce: 0x60400001bfd0>
2018-07-18 15:12:01.385957+0800 GCD[4125:168926] three:<DispatchOnce: 0x60400001b6a0>
2018-07-18 15:12:01.386151+0800 GCD[4125:168926] four :<DispatchOnce: 0x60400001aad0>
12、GCD快速迭代方法:dispatch_apply
  • dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
  • 因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是线程会等待dispatch_apply函数中全部任务执行完毕才会继续执行其他任务。
//12.GCD 快速迭代方法:dispatch_apply
- (instancetype)initDispatchApplyManager {
    if (self = [super init]) {
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        NSLog(@"apply---begin");
        dispatch_apply(5, queue, ^(size_t index) {
            NSLog(@"%ld --- %@",index,[NSThread currentThread]);
        });
        NSLog(@"apply---end");
    }
    return self;
}

输出结果:

2018-07-18 15:18:23.522423+0800 GCD[4125:168926] apply---begin
2018-07-18 15:18:23.524855+0800 GCD[4125:168926] 0 --- <NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:18:23.524875+0800 GCD[4125:194598] 1 --- <NSThread: 0x604000274b00>{number = 5, name = (null)}
2018-07-18 15:18:23.524926+0800 GCD[4125:180800] 2 --- <NSThread: 0x600000478000>{number = 6, name = (null)}
2018-07-18 15:18:23.524982+0800 GCD[4125:194608] 3 --- <NSThread: 0x604000271b80>{number = 7, name = (null)}
2018-07-18 15:18:23.525190+0800 GCD[4125:168926] 4 --- <NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:18:23.525362+0800 GCD[4125:168926] apply---end
13、GCD队列组:dispatch_group

如果我们在一个线程中异步执行几个耗时操作,但我们想等这几个耗时操作都完成之后再去执行其他操作,这时候我们就可以用到队列组dispatch_group了。

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

dispatch_group_notify
- (IBAction)dispatch_group_notify:(id)sender {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    dispatch_group_async(group, globalQueue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_group_async(group, globalQueue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_group_notify(group, mainQueue, ^{
        // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
        NSLog(@"group---end");
    });
}

输出结果:

2018-07-18 15:40:04.310925+0800 GCD[4125:168926] currentThread---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:40:04.311191+0800 GCD[4125:168926] group---begin
2018-07-18 15:40:06.316638+0800 GCD[4125:208985] 1---<NSThread: 0x6000004666c0>{number = 8, name = (null)}
2018-07-18 15:40:06.316645+0800 GCD[4125:180800] 2---<NSThread: 0x600000478000>{number = 6, name = (null)}
2018-07-18 15:40:08.319994+0800 GCD[4125:180800] 2---<NSThread: 0x600000478000>{number = 6, name = (null)}
2018-07-18 15:40:08.320003+0800 GCD[4125:208985] 1---<NSThread: 0x6000004666c0>{number = 8, name = (null)}
2018-07-18 15:40:10.321659+0800 GCD[4125:168926] 3---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:40:12.322667+0800 GCD[4125:168926] 3---<NSThread: 0x60000006ce00>{number = 1, name = main}
2018-07-18 15:40:12.323029+0800 GCD[4125:168926] group---end
  • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
  • 从dispatch_group_notify相关代码运行输出结果可以看出:
    当所有任务都执行完成之后,才执行dispatch_group_notify block 中的任务。
dispatch_group_wait
- (IBAction)dispatch_group_wait:(id)sender {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, globalQueue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });

    dispatch_group_async(group, globalQueue, ^{
        // 追加任务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-07-18 15:46:21.438719+0800 GCD[5117:214014] currentThread---<NSThread: 0x600000260d80>{number = 1, name = main}
2018-07-18 15:46:21.439135+0800 GCD[5117:214014] group---begin
2018-07-18 15:46:23.440102+0800 GCD[5117:214604] 1---<NSThread: 0x60400046d440>{number = 4, name = (null)}
2018-07-18 15:46:23.440101+0800 GCD[5117:214057] 2---<NSThread: 0x600000666f00>{number = 3, name = (null)}
2018-07-18 15:46:25.446875+0800 GCD[5117:214057] 2---<NSThread: 0x600000666f00>{number = 3, name = (null)}
2018-07-18 15:46:25.447304+0800 GCD[5117:214604] 1---<NSThread: 0x60400046d440>{number = 4, name = (null)}
2018-07-18 15:46:25.453273+0800 GCD[5117:214014] group---end
  • dispatch_group_wait会暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
dispatch_group_enter、dispatch_group_leave
- (IBAction)dispatch_groupClick:(id)sender {
    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-07-18 15:52:12.940112+0800 GCD[5242:219381] currentThread---<NSThread: 0x600000069200>{number = 1, name = main}
2018-07-18 15:52:12.940382+0800 GCD[5242:219381] group---begin
2018-07-18 15:52:14.946018+0800 GCD[5242:219651] 2---<NSThread: 0x600000469500>{number = 3, name = (null)}
2018-07-18 15:52:14.946018+0800 GCD[5242:219432] 1---<NSThread: 0x6000004762c0>{number = 4, name = (null)}
2018-07-18 15:52:16.951382+0800 GCD[5242:219432] 1---<NSThread: 0x6000004762c0>{number = 4, name = (null)}
2018-07-18 15:52:16.951397+0800 GCD[5242:219651] 2---<NSThread: 0x600000469500>{number = 3, name = (null)}
2018-07-18 15:52:18.953450+0800 GCD[5242:219381] 3---<NSThread: 0x600000069200>{number = 1, name = main}
2018-07-18 15:52:20.954745+0800 GCD[5242:219381] 3---<NSThread: 0x600000069200>{number = 1, name = main}
2018-07-18 15:52:20.954954+0800 GCD[5242:219381] group---end
  • 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相关代码运行结果中可以看出:当所有任务执行完成之后,才执行 dispatch_group_notify 中的任务。这里的dispatch_group_enter、dispatch_group_leave组合,其实等同于dispatch_group_async。
14、GCD信号量:dispatch_semaphore
  • 信号量就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
  • 类似锁机制,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
Dispatch Semaphore 提供了三个函数:
  • dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加1
  • dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
- (IBAction)useClick:(id)sender {
    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 NSInteger num = 0;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        num = 1;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,num = %ld",num);
}

输出结果:

2018-07-18 16:05:37.412573+0800 GCD[5472:229493] currentThread---<NSThread: 0x60400007cdc0>{number = 1, name = main}
2018-07-18 16:05:37.412940+0800 GCD[5472:229493] semaphore---begin
2018-07-18 16:05:39.417126+0800 GCD[5472:229532] 1---<NSThread: 0x60000007e080>{number = 3, name = (null)}
2018-07-18 16:05:39.417295+0800 GCD[5472:229493] semaphore---end,num = 1
  • 异步执行不会做任何等待,可以继续执行任务。异步执行将任务1追加到队列之后,不做等待,接着执行dispatch_semaphore_wait方法。此时 semaphore == 0,当前线程进入等待状态。然后,异步任务1开始执行。任务1执行到dispatch_semaphore_signal之后,此时总信号量 semaphore == 1,dispatch_semaphore_wait方法使总信号量减1,正在被阻塞的线程(主线程)恢复继续执行。最后打印semaphore---end,number = 1。这样就实现了线程同步,将异步执行任务转换为同步执行任务。
  • Dispatch Semaphore 在实际开发中主要用于:保持线程同步,将异步执行任务转换为同步执行任务;保证线程安全,为线程加锁。
15、取消GCD任务两种方法:
  • 第一种:iOS8之后可以调用dispatch_block_cancel来取消(需要注意必须用dispatch_block_create创建dispatch_block_t),dispatch_block_cancel也只能取消尚未执行的任务,对正在执行的任务不起作用。
  • 第二种:定义外部变量,用于标记block是否需要取消,该方法是模拟NSOperation,在执行block前先检查isCancelled = YES ?在block中及时的检测标记变量,当发现需要取消时,终止后续操作(如直接返回return)。

Demo地址:GCD

「欢迎指正交流」0_0

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

推荐阅读更多精彩内容

  • 本文首发于我的个人博客:「程序员充电站」[https://itcharge.cn]文章链接:「传送门」[https...
    ITCharge阅读 346,690评论 308 1,925
  • 文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。通过本文,您将了解到: 1. GCD 简介 2. G...
    晓_我想去环游世界阅读 1,128评论 2 8
  • “二喜、有庆不要偷懒,家珍、凤霞耕得好,苦根也行啊。”我说:“这牛究竟有多少名字?”老人回答:“这牛叫福贵,就一个...
    岩井俊一阅读 538评论 0 3
  • 失恋是亲密关系受损的一种,个体与父母分离时会进入两个阶段“抗议和失望”。而这同样能从失恋后的个体身上观察到。失恋是...
    夏灵岳阅读 240评论 0 0
  • 怎样才能幸福? 前两天一直在淘宝上购物,沉浸在买买买的乐趣里。 该买的都买了,想买的暂时没心情买。于是又沉寂了下来...
    桃子芸芸阅读 195评论 0 1