iOS

iOS-多线程(四)GCD

2019-07-17  本文已影响0人  厦门_小灰灰

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

我们将分为几个部分来介绍GCD。

线程与队列

这里简单解释一下队列。

队列是一种先进先出(FIFO)的数据结构,队列中的任务的开始的顺序和添加到队列中的顺序相同。
分类:串行队列,并发队列,主队列,全局队列;
队列中的任务,就是block代码块要执行的代码,其中有同步任务和异步任务。

串行队列:任务按先进先出的顺序一个一个执行,必须执行完前面的任务后才会执行后面任务;
并发队列:任务可以同时执行,具体是否同时执行要跟任务结合;
同步任务:会阻塞当前线程,不具备开启新的线程;
异步任务:不会阻塞当前线程,具备开启新的线程,具体是否开启了线程要结合队列;

任务主要是否开启新的线程,队列是可以开启多少条线程。

关于线程和队列以及任务的关系,我个人理解如下:

代码执行(任务)是在线程中执行的,队列是用于管理和存放任务的,线程负责去队列中取出任务并且执行。

多个队列的任务可以在一个线程上执行,一个队列的任务可以在多个线程上执行,。

简单示例代码与图解:

  1. 一个队列对应一个线程
//一个队列对应一个线程
- (void)singleQueueToSingleThread
{
    //主线程与主队列
    dispatch_async(dispatch_get_main_queue(), ^{
        [self logThreadAndTaskName:@"任务1"];
    });
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self logThreadAndTaskName:@"任务2"];
    });
}

- (void)logThreadAndTaskName:(NSString *)taskName
{
    NSLog(@"%@,线程:%@", taskName, [NSThread currentThread]);
}

打印结果:

2019-07-15 17:22:33.582148+0800 GCDDemo[8011:216536] 任务1,线程:<NSThread: 0x600001aaa940>{number = 1, name = main}
2019-07-15 17:22:33.582369+0800 GCDDemo[8011:216536] 任务2,线程:<NSThread: 0x600001aaa940>{number = 1, name = main}
一个队列对应一个线程.png
  1. 多个队列对应一个线程
//多个队列对应一个线程
- (void)multipleQueueToSingleThread
{
    //创建一个队列
    dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_SERIAL);
    
    //主队列 <-> 主线程
    [self logThreadAndTaskName:@"任务1"];
    
    //创建的队列demoQueue <-> 主线程
    //由于是同步任务,所以不会创建线程。
    dispatch_sync(queue, ^{
        [self logThreadAndTaskName:@"任务2"];
    });
}

打印结果:

2019-07-15 17:34:00.470162+0800 GCDDemo[8194:221950] 任务1,线程:<NSThread: 0x600000a5e840>{number = 1, name = main}
2019-07-15 17:34:00.470345+0800 GCDDemo[8194:221950] 任务2,线程:<NSThread: 0x600000a5e840>{number = 1, name = main}
多个队列对应一个线程.png
  1. 一个队列对应多个线程
//一个队列对应多个线程
- (void)singleQueueToMultipleThread
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    //由于是一部任务,并且是在并发队列上,所以会创建线程。
    dispatch_async(queue, ^{
        [self logThreadAndTaskName:@"任务1"];
    });
    
    dispatch_async(queue, ^{
        [self logThreadAndTaskName:@"任务2"];
    });
}

打印结果:

2019-07-15 17:43:49.686754+0800 GCDDemo[8344:227351] 任务2,线程:<NSThread: 0x600003165040>{number = 4, name = (null)}
2019-07-15 17:43:49.686754+0800 GCDDemo[8344:227352] 任务1,线程:<NSThread: 0x60000310fa00>{number = 3, name = (null)}
一个队列对应多个线程.png
  1. 多个队列对应多个线程
//多个队列对应多个线程
- (void)multipleQueueToMultipleThread
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    //主队列 <-> 主线程
    [self logThreadAndTaskName:@"任务1"];
    
    //由于是一部任务,并且是在并发队列上,所以会创建线程。
    dispatch_async(queue, ^{
        [self logThreadAndTaskName:@"任务2"];
    });
}

打印结果:

2019-07-15 18:23:47.223693+0800 GCDDemo[8882:239964] 任务1,线程:<NSThread: 0x6000022d2440>{number = 1, name = main}
2019-07-15 18:23:47.224392+0800 GCDDemo[8882:240023] 任务2,线程:<NSThread: 0x600002299dc0>{number = 3, name = (null)}
多个队列对应多个线程.png

通过上面的四个例子,验证了个人理解的线程与队列之间的关系,如果有错误还请提出。

接下来看一下,队列与任务之间的排列组合。


队列与任务排列组合.png

简单示例:

  1. 串行队列 + 同步任务
    不开启新的线程、任务按顺序执行
//1.串行队列 + 同步任务
- (void)syncSerial
{
    NSLog(@"开始");
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 22:29:32.459408+0800 GCDDemo[10355:278599] 开始
2019-07-15 22:29:33.461224+0800 GCDDemo[10355:278599] 任务一,线程:<NSThread: 0x6000031813c0>{number = 1, name = main}
2019-07-15 22:29:34.463121+0800 GCDDemo[10355:278599] 任务一,线程:<NSThread: 0x6000031813c0>{number = 1, name = main}
2019-07-15 22:29:35.465262+0800 GCDDemo[10355:278599] 任务二,线程:<NSThread: 0x6000031813c0>{number = 1, name = main}
2019-07-15 22:29:36.465848+0800 GCDDemo[10355:278599] 任务二,线程:<NSThread: 0x6000031813c0>{number = 1, name = main}
2019-07-15 22:29:36.466247+0800 GCDDemo[10355:278599] 结束
  1. 串行队列 + 异步任务
    开启新的线程、任务顺序执行
//2.串行队列 + 异步任务
- (void)asyncSerial
{
    NSLog(@"开始");
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 22:32:16.952311+0800 GCDDemo[10398:280334] 开始
2019-07-15 22:32:16.952538+0800 GCDDemo[10398:280334] 结束
2019-07-15 22:32:17.955751+0800 GCDDemo[10398:280395] 任务一,线程:<NSThread: 0x600000a82e40>{number = 3, name = (null)}
2019-07-15 22:32:18.957896+0800 GCDDemo[10398:280395] 任务一,线程:<NSThread: 0x600000a82e40>{number = 3, name = (null)}
2019-07-15 22:32:19.961756+0800 GCDDemo[10398:280395] 任务二,线程:<NSThread: 0x600000a82e40>{number = 3, name = (null)}
2019-07-15 22:32:20.962017+0800 GCDDemo[10398:280395] 任务二,线程:<NSThread: 0x600000a82e40>{number = 3, name = (null)}
  1. 并发队列 + 同步任务
    不开启新的线程、任务按顺序执行
//3.并发队列 + 同步任务
- (void)syncConcurrent
{
    NSLog(@"开始");
    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 22:35:33.502311+0800 GCDDemo[10450:282193] 开始
2019-07-15 22:35:34.503702+0800 GCDDemo[10450:282193] 任务一,线程:<NSThread: 0x6000023ea900>{number = 1, name = main}
2019-07-15 22:35:35.505127+0800 GCDDemo[10450:282193] 任务一,线程:<NSThread: 0x6000023ea900>{number = 1, name = main}
2019-07-15 22:35:36.506593+0800 GCDDemo[10450:282193] 任务二,线程:<NSThread: 0x6000023ea900>{number = 1, name = main}
2019-07-15 22:35:37.508095+0800 GCDDemo[10450:282193] 任务二,线程:<NSThread: 0x6000023ea900>{number = 1, name = main}
2019-07-15 22:35:37.508388+0800 GCDDemo[10450:282193] 结束
  1. 并发队列 + 异步任务
    开启新的线程、任务同时执行
//4.并发队列 + 异步任务
- (void)asyncConcurrent
{
    NSLog(@"开始");
    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 22:36:57.734245+0800 GCDDemo[10477:283114] 开始
2019-07-15 22:36:57.734442+0800 GCDDemo[10477:283114] 结束
2019-07-15 22:36:58.735699+0800 GCDDemo[10477:283151] 任务二,线程:<NSThread: 0x600000bce000>{number = 3, name = (null)}
2019-07-15 22:36:58.735699+0800 GCDDemo[10477:283153] 任务一,线程:<NSThread: 0x600000bf6a40>{number = 4, name = (null)}
2019-07-15 22:36:59.739590+0800 GCDDemo[10477:283151] 任务二,线程:<NSThread: 0x600000bce000>{number = 3, name = (null)}
2019-07-15 22:36:59.739590+0800 GCDDemo[10477:283153] 任务一,线程:<NSThread: 0x600000bf6a40>{number = 4, name = (null)}
  1. 主队列 + 异步任务
    主线程执行任务,主队列的任务都是在主线程执行,任务按顺序执行
//5.主队列 + 异步任务
- (void)asyncMain
{
    NSLog(@"开始");
    //获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 22:44:24.649360+0800 GCDDemo[10598:287127] 开始
2019-07-15 22:44:24.649608+0800 GCDDemo[10598:287127] 结束
2019-07-15 22:44:25.662664+0800 GCDDemo[10598:287127] 任务一,线程:<NSThread: 0x60000168a8c0>{number = 1, name = main}
2019-07-15 22:44:26.664320+0800 GCDDemo[10598:287127] 任务一,线程:<NSThread: 0x60000168a8c0>{number = 1, name = main}
2019-07-15 22:44:27.665317+0800 GCDDemo[10598:287127] 任务二,线程:<NSThread: 0x60000168a8c0>{number = 1, name = main}
2019-07-15 22:44:28.666551+0800 GCDDemo[10598:287127] 任务二,线程:<NSThread: 0x60000168a8c0>{number = 1, name = main}
  1. 主队列 + 同步任务
    队列引起死锁
//6.主队列 + 同步任务
- (void)syncMain
{
    NSLog(@"开始");
    //获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务一"];
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务二"];
        }
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-15 23:00:20.942927+0800 GCDDemo[10815:293655] 开始
(lldb)  死锁导致了闪退

由于全局队列就是并发队列,这两种组合就不在列举了。

小小总结:

  1. 同步任务不具备开启线程,异步任务具备开启线程,串行队列具备开一条线程,并发队列具备开多条线程;
  2. 主队列上的任务只能在主线程中执行;

死锁的原因

上面列举的 主队列 + 同步任务 会导致死锁,我们一起分析分析怎么导致了死锁。

我上面写的是 队列 引起死锁,下面我会通过图的形式来说说我自己的理解。
先用这段会导致死锁的代码来说明。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self testDeadlock];
}

- (void)testDeadlock
{
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

为什么要把viewDidLoad方法也放进来,主要是为了更好的解释死锁。
我们知道在程序运行起来的时候,已经创建了主线程和主队列,现在viewDidLoad方法的代码,可以想象成是一个任务,这个任务加在了主队列上,然后在主线程中执行。

简单的画了一个图


代码执行.png
  1. 一开始现在主线程中执行testDeadlock这个方法,并且testDeadlock方法本身就是一个任务,处于主队列中,因为还没有执行完,所以还没出队列;
  2. 当执行到
   dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });

NSLog(@"2");这个任务添加到了主队列中,其中队列是FIFO(先进先出),所以 这个任务是加在 testDeadlock这个任务之后;

  1. 因为调用了dispatch_sync 是要等NSLog(@"2");任务执行结束后才会返回,但是现在等于testDeadlock方法这个任务还没执行完,要等NSLog(@"2");任务执行完成才能出队列,而NSLog(@"2");任务要等testDeadlock方法这个任务执行完,才能执行,所以就出现了相互等待,所以导致了死锁。

这是苹果对dispatch_sync函数解释提到的。


dispatch_sync.png

可以通过另外一个示例来验证。
将上面代码的主队列换成自己创建的串行队列。

dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(queue, ^{
    NSLog(@"2");
});
NSLog(@"3");

上面的代码不会出现死锁,结果是 1 -> 2 -> 3。
因为现在队列中的任务不会出现相互等待的,所以没有出现死锁的现象。

以上是个人理解,如有错误,还请提出。

常见使用

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
        dispatch_block_t block);

主要作用:在指定时间之后执行某个任务。
注意:dispatch_after函数中的任务并不是指定时间之后马上执行,而是在指定时间之后将任务添加到指定队列中。所以这个指定时间不是非常准确。

- (void)testDispatchAfter
{
    NSLog(@"开始");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"hello dispatch_after");
        NSLog(@"执行完成");
    });
}

打印结果:

2019-07-17 02:17:49.025143+0800 GCDDemo[7269:176029] 开始
2019-07-17 02:17:50.114528+0800 GCDDemo[7269:176029] hello dispatch_after
2019-07-17 02:17:50.114828+0800 GCDDemo[7269:176029] 执行完成

可以看出,不是整整的1.0s,还是有一点误差。

主要作用:保证某段代码在程序运行过程中只被执行1次,保证线程安全。

- (void)testDispatchOnce
{
    //线程安全
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //这里写入要执行一次的代码
    });
}

需求:我们要分别异步执行两个耗时的任务,然后当两个耗时任务都完成的时候,可以接收到通知,然后我们在做其他的任务,这种场景就可以用到队列组。

或者

使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现,其中dispatch_group_enter指示一个block任务已经进入group状态,dispatch_group_leave指示一个block任务执行完毕,可以退出组管理,它们类似于任务计数器,协同队列组管理自己的任务,当没有任务,即任务计数为0时调用dispatch_group_notify函数;

dispatch_group_async
- (void)testDispatchGroupWithAsync
{
    //创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    //模拟两个耗时操作
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
    });
    
    //耗时任务完成后,通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"耗时任务结束,在这里做相关的处理。");
    });
    
    NSLog(@"这里会先执行哦!!!");
}

打印结果:

2019-07-17 10:08:16.999686+0800 GCDDemo[2629:21572] 这里会先执行哦!!!
2019-07-17 10:08:18.001221+0800 GCDDemo[2629:21613] 任务1,线程:<NSThread: 0x6000024b86c0>{number = 4, name = (null)}
2019-07-17 10:08:18.001226+0800 GCDDemo[2629:21612] 任务2,线程:<NSThread: 0x6000024c9b80>{number = 3, name = (null)}
2019-07-17 10:08:19.001984+0800 GCDDemo[2629:21613] 任务1,线程:<NSThread: 0x6000024b86c0>{number = 4, name = (null)}
2019-07-17 10:08:19.001984+0800 GCDDemo[2629:21612] 任务2,线程:<NSThread: 0x6000024c9b80>{number = 3, name = (null)}
2019-07-17 10:08:19.002325+0800 GCDDemo[2629:21572] 耗时任务结束,在这里做相关的处理。
dispatch_group_enter,dispatch_group_leave
- (void)testDispatchGroupWithEnterAndLeave
{
    //创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    //模拟两个耗时操作
    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
        dispatch_group_leave(group);
    });
    
    //耗时任务完成后,通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"耗时任务结束,在这里做相关的处理。");
    });
    
    NSLog(@"这里会先执行哦!!!");
}

打印结果:

2019-07-17 10:17:56.364714+0800 GCDDemo[2774:25712] 这里会先执行哦!!!
2019-07-17 10:17:57.365984+0800 GCDDemo[2774:25752] 任务1,线程:<NSThread: 0x6000006575c0>{number = 3, name = (null)}
2019-07-17 10:17:57.365994+0800 GCDDemo[2774:25751] 任务2,线程:<NSThread: 0x600000658180>{number = 4, name = (null)}
2019-07-17 10:17:58.368892+0800 GCDDemo[2774:25751] 任务2,线程:<NSThread: 0x600000658180>{number = 4, name = (null)}
2019-07-17 10:17:58.368838+0800 GCDDemo[2774:25752] 任务1,线程:<NSThread: 0x6000006575c0>{number = 3, name = (null)}
2019-07-17 10:17:58.369200+0800 GCDDemo[2774:25712] 耗时任务结束,在这里做相关的处理。

注意上面的代码是不会阻塞当前线程!从打印的结果可以看出。

dispatch_group_wait
- (void)testDispatchGroupWithAsyncAndWait
{
    //创建一个队列组
    dispatch_group_t group = dispatch_group_create();
    
    //模拟两个耗时操作
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
    });
    
    //一直等待完成
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //指定等待时间
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    long watiTime = dispatch_group_wait(group, time);
    
    if ( watiTime != 0 ) {
        NSLog(@"等待时间结束了,但是耗时任务还未执行完成,不阻塞线程,往下继续执行代码");
    }
    
    //耗时任务完成后,通知
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"耗时任务结束,在这里做相关的处理。");
    });
    
    NSLog(@"这里会先执行吗?不会,因为dispatch_group_wait会阻塞当前线程。");
}

打印结果:

2019-07-17 10:56:24.764994+0800 GCDDemo[3371:43061] 任务1,线程:<NSThread: 0x600000ac8cc0>{number = 4, name = (null)}
2019-07-17 10:56:24.765006+0800 GCDDemo[3371:43062] 任务2,线程:<NSThread: 0x600000acf380>{number = 3, name = (null)}
2019-07-17 10:56:25.764005+0800 GCDDemo[3371:43016] 等待时间结束了,但是耗时任务还未执行完成,不阻塞线程,往下继续执行代码
2019-07-17 10:56:25.764370+0800 GCDDemo[3371:43016] 这里会先执行吗?不会,因为dispatch_group_wait会阻塞当前线程。
2019-07-17 10:56:25.765917+0800 GCDDemo[3371:43061] 任务1,线程:<NSThread: 0x600000ac8cc0>{number = 4, name = (null)}
2019-07-17 10:56:25.765917+0800 GCDDemo[3371:43062] 任务2,线程:<NSThread: 0x600000acf380>{number = 3, name = (null)}
2019-07-17 10:56:25.779926+0800 GCDDemo[3371:43016] 耗时任务结束,在这里做相关的处理。

从打印结果看,dispatch_group_wait会阻塞当前线程。

dispatch_group_async 和 dispatch_group_enter,dispatch_group_leave实质都是一样的,但是dispatch_group_enter,dispatch_group_leave会相对较为灵活,因为可以执行多层嵌套后在leave。dispatch_group_enter和dispatch_group_leave需要配对,不然会报错。

需求:任务有依赖关系的时候,比如任务1和2执行完毕后才能执行任务3和4,这时候我们可以用到这个函数——栅栏函数。
其中,
dispatch_barrier_sync:提交一个栅栏函数在执行中,它会等待栅栏函数执行完再去执行下一行代码;
dispatch_barrier_async:提交一个栅栏函数在异步执行中,它会立马返回开始执行下一行代码;

同步栅栏示例:

- (void)testDispatchBarrierSync
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
    });
    
    NSLog(@"同步栅栏 开始");
    dispatch_barrier_sync(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [self logThreadAndTaskName:@"执行栅栏函数"];
        }
    });
    NSLog(@"同步栅栏 结束");
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务3"];
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务4"];
        }
    });
    
}

打印结果:

2019-07-17 18:27:11.531699+0800 GCDDemo[4219:67676] 同步栅栏 开始
2019-07-17 18:27:12.532039+0800 GCDDemo[4219:67714] 任务1,线程:<NSThread: 0x600001acf3c0>{number = 4, name = (null)}
2019-07-17 18:27:12.532041+0800 GCDDemo[4219:67713] 任务2,线程:<NSThread: 0x600001ac9c40>{number = 3, name = (null)}
2019-07-17 18:27:13.533015+0800 GCDDemo[4219:67713] 任务2,线程:<NSThread: 0x600001ac9c40>{number = 3, name = (null)}
2019-07-17 18:27:13.533015+0800 GCDDemo[4219:67714] 任务1,线程:<NSThread: 0x600001acf3c0>{number = 4, name = (null)}
2019-07-17 18:27:13.533328+0800 GCDDemo[4219:67676] 执行栅栏函数,线程:<NSThread: 0x600001a90c40>{number = 1, name = main}
2019-07-17 18:27:13.533467+0800 GCDDemo[4219:67676] 执行栅栏函数,线程:<NSThread: 0x600001a90c40>{number = 1, name = main}
2019-07-17 18:27:13.533580+0800 GCDDemo[4219:67676] 同步栅栏 结束
2019-07-17 18:27:14.536702+0800 GCDDemo[4219:67714] 任务4,线程:<NSThread: 0x600001acf3c0>{number = 4, name = (null)}
2019-07-17 18:27:14.536711+0800 GCDDemo[4219:67713] 任务3,线程:<NSThread: 0x600001ac9c40>{number = 3, name = (null)}
2019-07-17 18:27:15.538313+0800 GCDDemo[4219:67714] 任务4,线程:<NSThread: 0x600001acf3c0>{number = 4, name = (null)}
2019-07-17 18:27:15.538313+0800 GCDDemo[4219:67713] 任务3,线程:<NSThread: 0x600001ac9c40>{number = 3, name = (null)}

同步栅栏函数等待执行完再去执行下一行代码。

异步栅栏示例:

- (void)testDispatchBarrierAsync
{
    //创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("demoQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
    });
    
    NSLog(@"异步栅栏 开始");
    dispatch_barrier_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [self logThreadAndTaskName:@"执行栅栏函数"];
        }
    });
    NSLog(@"异步栅栏 结束");
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务3"];
        }
    });
    
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务4"];
        }
    });
    
}

打印结果:

2019-07-17 18:31:35.449327+0800 GCDDemo[4355:70639] 异步栅栏 开始
2019-07-17 18:31:35.449596+0800 GCDDemo[4355:70639] 异步栅栏 结束
2019-07-17 18:31:36.451865+0800 GCDDemo[4355:70691] 任务2,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:36.451870+0800 GCDDemo[4355:70688] 任务1,线程:<NSThread: 0x600001ca8300>{number = 3, name = (null)}
2019-07-17 18:31:37.456683+0800 GCDDemo[4355:70688] 任务1,线程:<NSThread: 0x600001ca8300>{number = 3, name = (null)}
2019-07-17 18:31:37.456684+0800 GCDDemo[4355:70691] 任务2,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:37.457066+0800 GCDDemo[4355:70691] 执行栅栏函数,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:37.457280+0800 GCDDemo[4355:70691] 执行栅栏函数,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:38.462838+0800 GCDDemo[4355:70688] 任务4,线程:<NSThread: 0x600001ca8300>{number = 3, name = (null)}
2019-07-17 18:31:38.462838+0800 GCDDemo[4355:70691] 任务3,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:39.468304+0800 GCDDemo[4355:70691] 任务3,线程:<NSThread: 0x600001cac900>{number = 4, name = (null)}
2019-07-17 18:31:39.468304+0800 GCDDemo[4355:70688] 任务4,线程:<NSThread: 0x600001ca8300>{number = 3, name = (null)}

异步栅栏函数将任务添加到队列后立即返回执行下一行代码。

注:

在使用栅栏函数时.使用自定义队列才有意义,如果用的是串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用。

需求:保持线程同步,将异步执行任务转换为同步执行任务;线程安全,为线程加锁;

信号量常用函数:

    /*
     传入的参数为long类型,输出一个dispatch_semaphore_t类型且值为value的信号量。
     值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL
     */
    dispatch_semaphore_t
    dispatch_semaphore_create(long value);
    
    /*
     如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
     如果desema的值为0,那么这个函数就阻塞当前线程等待timeout;
     */
    long
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
    
    /*
     传入的信号量dsema的值加1;
     */
    long
    dispatch_semaphore_signal(dispatch_semaphore_t dsema);

简单示例:

- (void)testDispatchSemaphore
{

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务1"];
        }
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务2"];
        }
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        for (NSInteger i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:1];
            [self logThreadAndTaskName:@"任务3"];
        }
        dispatch_semaphore_signal(semaphore);
    });
}

打印结果:

2019-07-17 19:28:11.289432+0800 GCDDemo[4842:87233] 任务1,线程:<NSThread: 0x600001a31a40>{number = 3, name = (null)}
2019-07-17 19:28:11.289445+0800 GCDDemo[4842:87234] 任务2,线程:<NSThread: 0x600001a01b00>{number = 4, name = (null)}
2019-07-17 19:28:12.294988+0800 GCDDemo[4842:87233] 任务1,线程:<NSThread: 0x600001a31a40>{number = 3, name = (null)}
2019-07-17 19:28:12.294988+0800 GCDDemo[4842:87234] 任务2,线程:<NSThread: 0x600001a01b00>{number = 4, name = (null)}
2019-07-17 19:28:13.298775+0800 GCDDemo[4842:87236] 任务3,线程:<NSThread: 0x600001a08580>{number = 5, name = (null)}
2019-07-17 19:28:14.299092+0800 GCDDemo[4842:87236] 任务3,线程:<NSThread: 0x600001a08580>{number = 5, name = (null)}

可见任务3,是在任务1和任务2执行完成后才执行。

使用信号量可以实现对最大并发数的控制。

dispatch_apply是按照指定的次数将指定的任务追加到指定的队列中运行block任务指定次数,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行指定次数后才返回。

串行队列示例:

- (void)testDispatchApplySync
{
    
    //创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"开始");
    
    dispatch_apply(5, queue, ^(size_t index) {
        [self logThreadAndTaskName:[NSString stringWithFormat:@"任务%lu", index + 1]];
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-17 23:07:12.196799+0800 GCDDemo[5383:103942] 开始
2019-07-17 23:07:12.197052+0800 GCDDemo[5383:103942] 任务1,线程:<NSThread: 0x6000025ce840>{number = 1, name = main}
2019-07-17 23:07:12.197166+0800 GCDDemo[5383:103942] 任务2,线程:<NSThread: 0x6000025ce840>{number = 1, name = main}
2019-07-17 23:07:12.197276+0800 GCDDemo[5383:103942] 任务3,线程:<NSThread: 0x6000025ce840>{number = 1, name = main}
2019-07-17 23:07:12.197382+0800 GCDDemo[5383:103942] 任务4,线程:<NSThread: 0x6000025ce840>{number = 1, name = main}
2019-07-17 23:07:12.197508+0800 GCDDemo[5383:103942] 任务5,线程:<NSThread: 0x6000025ce840>{number = 1, name = main}
2019-07-17 23:07:12.197609+0800 GCDDemo[5383:103942] 结束

并发队列示例:

- (void)testDispatchApplyAsync
{
    
    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"开始");
    
    dispatch_apply(5, queue, ^(size_t index) {
        [self logThreadAndTaskName:[NSString stringWithFormat:@"任务%lu", index + 1]];
    });
    
    NSLog(@"结束");
}

打印结果:

2019-07-17 23:08:47.240222+0800 GCDDemo[5416:105046] 开始
2019-07-17 23:08:47.240471+0800 GCDDemo[5416:105046] 任务1,线程:<NSThread: 0x600003e52840>{number = 1, name = main}
2019-07-17 23:08:47.240509+0800 GCDDemo[5416:105090] 任务2,线程:<NSThread: 0x600003e38d00>{number = 3, name = (null)}
2019-07-17 23:08:47.240543+0800 GCDDemo[5416:105088] 任务3,线程:<NSThread: 0x600003e7ec00>{number = 4, name = (null)}
2019-07-17 23:08:47.240606+0800 GCDDemo[5416:105046] 任务4,线程:<NSThread: 0x600003e52840>{number = 1, name = main}
2019-07-17 23:08:47.240643+0800 GCDDemo[5416:105090] 任务5,线程:<NSThread: 0x600003e38d00>{number = 3, name = (null)}
2019-07-17 23:08:47.240751+0800 GCDDemo[5416:105046] 结束

使用dispatch_apply,GCD会管理并发,能够避免一些创建非常多线程的情况发生。
如:

for (int i = 0; i < 100; ++i) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%d", i);
    });
}

Demo传送门

GCD部分先总结到这里,over!

上一篇下一篇

猜你喜欢

热点阅读