GCD篇(2)

这里的运行顺序都是指执行顺序,如果是异步回调方式的执行顺序要分情况。

1.group之并发异步

dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1——准备休眠3秒");
    sleep(3);
    NSLog(@"任务1——完成");
});

NSLog(@"主线程——准备休眠5秒");
sleep(5);
NSLog(@"主线休眠结束");

dispatch_group_async(group, queue, ^{// 等主线程休眠结束后才会开启新的线程
    NSLog(@"任务2——准备休眠10秒");
    sleep(10);
    NSLog(@"任务2——完成");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任务组完成");
});

NSLog(@"主线程结束");

运行结果

2018-04-18 14:50:56.552841+0800 GCDDEMO[95861:36902605] 主线程——准备休眠5秒
2018-04-18 14:50:56.552841+0800 GCDDEMO[95861:36902640] 任务1——准备休眠3秒
2018-04-18 14:50:59.557429+0800 GCDDEMO[95861:36902640] 任务1——完成
2018-04-18 14:51:01.554070+0800 GCDDEMO[95861:36902605] 主线休眠结束
2018-04-18 14:51:01.554208+0800 GCDDEMO[95861:36902605] 主线程结束
2018-04-18 14:51:01.554225+0800 GCDDEMO[95861:36902640] 任务2——准备休眠10秒
2018-04-18 14:51:11.558221+0800 GCDDEMO[95861:36902640] 任务2——完成
2018-04-18 14:51:11.558424+0800 GCDDEMO[95861:36902640] 任务组完成
  • dispatch_group_notify任务组的结束通知可以添加多次、并且会多次调用。
  • 类似于NSOperation中的依赖、GCD的group监听是可以追加的。只要任务组中的任务没有全部完成、group完成的监听就不会被调用、哪怕是后追加的任务。
dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1——准备休眠3秒");
    sleep(3);
    NSLog(@"任务1——完成");
});

NSLog(@"添加完成监听");
dispatch_group_notify(group, queue, ^{
    NSLog(@"任务组完成");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务2——准备休眠5秒");
    sleep(10);
    NSLog(@"任务2——完成");
});

NSLog(@"主线程结束");   

运行结果

2018-04-18 15:14:51.626574+0800 GCDDEMO[96182:36985276] 添加完成监听
2018-04-18 15:14:51.626574+0800 GCDDEMO[96182:36985448] 任务1——准备休眠3秒
2018-04-18 15:14:51.626747+0800 GCDDEMO[96182:36985276] 主线程结束
2018-04-18 15:14:51.626759+0800 GCDDEMO[96182:36985447] 任务2——准备休眠10秒
2018-04-18 15:14:54.628660+0800 GCDDEMO[96182:36985448] 任务1——完成
2018-04-18 15:15:01.630341+0800 GCDDEMO[96182:36985447] 任务2——完成
2018-04-18 15:15:01.630496+0800 GCDDEMO[96182:36985447] 任务组完成 
  • 任务组是可以跨队列监听的,不知道是不是这么用的?
    dispatch_queue_t queue = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("queue_test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue2, ^{
        NSLog(@"任务1——准备休眠3秒");
        sleep(3);
        NSLog(@"任务1——完成");
    });
    
    NSLog(@"添加完成监听");
    dispatch_group_notify(group, queue, ^{
        NSLog(@"任务组完成");
    });
    
    NSLog(@"主线程结束");

运行结果

2018-04-18 15:36:39.651263+0800 GCDDEMO[96416:37053885] 添加完成监听
2018-04-18 15:36:39.651266+0800 GCDDEMO[96416:37054026] 任务1——准备休眠3秒
2018-04-18 15:36:39.651413+0800 GCDDEMO[96416:37053885] 主线程结束
2018-04-18 15:36:42.654309+0800 GCDDEMO[96416:37054026] 任务1——完成
2018-04-18 15:36:42.654467+0800 GCDDEMO[96416:37054026] 任务组完成

2.快速遍历dispatch_apply
GCD提供了多线程快速遍历的方法。需要注意的是:

  • 由于多线程遍历、输出的下标未必按照顺序排列
  • 本质上是一个同步任务,而内部会使用一个并行队列用异步任务进行遍历(所以如果需要的话,自己在外部开辟一个新的异步任务)。
    NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        
        dispatch_apply([array count], queue, ^(size_t index) {
            NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
        });
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主线程执行用户界面更新等操作");
        });
        
    });

运行结果

2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234099] 4: e
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234101] 5: f
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234102] 0: a
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234108] 2: c
2018-04-18 16:33:52.624279+0800 GCDDEMO[410:37234109] 1: b
2018-04-18 16:33:52.624275+0800 GCDDEMO[410:37234100] 3: d
2018-04-18 16:33:52.624320+0800 GCDDEMO[410:37234124] 6: g
2018-04-18 16:33:52.624322+0800 GCDDEMO[410:37234125] 7: h
2018-04-18 16:33:52.624403+0800 GCDDEMO[410:37234101] 8: i
2018-04-18 16:33:52.624403+0800 GCDDEMO[410:37234099] 9: j
2018-04-18 16:33:52.640095+0800 GCDDEMO[410:37234054] 回到主线程执行用户界面更新等操作

3.GCD定时器

// 暂停定时器
- (IBAction)pause:(id)sender {
    if (_timer) {
        dispatch_suspend(_timer);
    }
}

// 恢复定时器
- (IBAction)resume:(id)sender {// pause 一定要和resume成对使用,即使暂停后销毁stop也不行
    if (_timer) {
        dispatch_resume(_timer);
    }
}

// 销毁定时器
- (IBAction)stop:(id)sender {
    if (_timer) {
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

// 创建定时器
- (IBAction)start:(id)sender {
    // 创建定时器,(dispatch_source_t本质是OC对象)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    
    // 设置定时器的时间参数,时间参数一般是纳秒,(1秒 == 10的9次方纳秒)为单位
    // 何时开始
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (ino64_t)(1.0 * NSEC_PER_SEC));
    // 时间间隔
    uint64_t interval = (uint64_t)(2.0 * NSEC_PER_SEC);
    // 设置参数
    dispatch_source_set_timer(self.timer, start, interval, 0);
    // 设置回调,即设置需要定时器定时执行的操作
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------");
    });
    
    dispatch_resume(self.timer);
}

也可以用于任务挂起和恢复

    dispatch_queue_t myQueue;
    myQueue = dispatch_queue_create("queue", NULL);
    //挂起队列
    NSLog(@"1");
    dispatch_sync(myQueue, ^(){
        NSLog(@"挂起任务之前");
    });
    NSLog(@"2");
    dispatch_suspend(myQueue);
    NSLog(@"3");
    dispatch_sync(myQueue, ^(){
        NSLog(@"挂起任务之后");
    });
    NSLog(@"4");
    //恢复队列
    dispatch_resume(myQueue);

执行结果

2018-04-19 09:47:11.089857+0800 GCDDEMO[17482:38678962] 1
2018-04-19 09:47:11.090024+0800 GCDDEMO[17482:38678962] 挂起任务之前
2018-04-19 09:47:11.090105+0800 GCDDEMO[17482:38678962] 2
2018-04-19 09:47:11.090162+0800 GCDDEMO[17482:38678962] 3

可以看到输出3之后后面就没有再输出任何log,所以需要外界一个条件触发dispatch_resume来恢复队列

4.信号量semaphore
信号量类似于锁
简单来讲,信号量为0则阻塞线程,大于0则不会阻塞。那么我们可以通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步
GCD中的信号量含有三个函数

  • dispatch_semaphore_create创建一个semaphore信号量
  • dispatch_semaphore_signal发送一个信号让信号量+1
  • dispatch_semaphore_wait如果信号量计数为0则阻塞等待、否则通过。

下面举两个例子

  • 用semaphore控制并发数,通过设置信号量初始值,达到GCD的并发。
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建信号量,并且设置值为10
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 30; i++)
    {   // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_group_async(group, queue, ^{
            NSLog(@"%i",i);
            sleep(i);
            // 每次发送信号则semaphore会+1,
            dispatch_semaphore_signal(semaphore);
        });
    }
  • 异步任务转同步
    通过在方法末尾用信号量阻塞、知道异步请求完成后通过。
    配合GCD任务组、NSOperation的依赖,可以达到多网络请求后的同步操作
    NSString *urlStr = @"https://www.baidu.com";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    
    for (int i = 0; i < 10; i++) {
        
        
        NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog(@"%d--%d",i,i);
            dispatch_semaphore_signal(sem);
        }];
        [task resume];
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"end~~~");
    });

5.线程栅栏dispatch_barrier
线程栅栏可以阻塞某个queue中任务的执行直到queue中的栅栏之前任务被执行完毕。

    dispatch_queue_t queue = dispatch_queue_create("test_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 1; i <= 3; i ++) {
        dispatch_async(queue, ^{
            sleep(3);
            NSLog(@"任务%d结束",i);
        });
    }
    NSLog(@"代码经过栅栏");
    dispatch_barrier_sync(queue, ^{
        sleep(2);
        NSLog(@"栅栏结束");
    });
    NSLog(@"代码通过栅栏");
    
    for (int i = 4; i <= 6; i ++) {
        dispatch_async(queue, ^{
            sleep(3);
            NSLog(@"任务%d结束",i);
        });
    }
    NSLog(@"代码结束");

运行结果

2018-04-19 09:20:03.953580+0800 GCDDEMO[17144:38579855] 代码经过栅栏
2018-04-19 09:20:06.956880+0800 GCDDEMO[17144:38579951] 任务3结束
2018-04-19 09:20:06.956889+0800 GCDDEMO[17144:38579948] 任务1结束
2018-04-19 09:20:06.956880+0800 GCDDEMO[17144:38579950] 任务2结束
2018-04-19 09:20:08.958230+0800 GCDDEMO[17144:38579855] 栅栏结束
2018-04-19 09:20:08.958366+0800 GCDDEMO[17144:38579855] 代码通过栅栏
2018-04-19 09:20:08.958517+0800 GCDDEMO[17144:38579855] 代码结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579950] 任务6结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579951] 任务5结束
2018-04-19 09:20:11.962660+0800 GCDDEMO[17144:38579949] 任务4结束

如果把dispatch_barrier_sync改成dispatch_barrier_async之后运行结果

2018-04-19 09:21:31.528745+0800 GCDDEMO[17183:38585946] 代码经过栅栏
2018-04-19 09:21:31.528887+0800 GCDDEMO[17183:38585946] 代码通过栅栏
2018-04-19 09:21:31.528990+0800 GCDDEMO[17183:38585946] 代码结束
2018-04-19 09:21:34.531305+0800 GCDDEMO[17183:38586062] 任务1结束
2018-04-19 09:21:34.531305+0800 GCDDEMO[17183:38586064] 任务2结束
2018-04-19 09:21:34.531306+0800 GCDDEMO[17183:38586061] 任务3结束
2018-04-19 09:21:36.535029+0800 GCDDEMO[17183:38586064] 栅栏结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586064] 任务4结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586062] 任务6结束
2018-04-19 09:21:39.538077+0800 GCDDEMO[17183:38586061] 任务5结束

很明显的区别在于:同步栅栏会阻塞之后普通代码的执行、异步栅栏则不会。应用栅栏的特性、我们可以更好的做一些线程同步。某些情况下不需要写好几层任务组来同步任务。

例如4/5/6任务想要等待1/2/3任务。
用任务组的话、需要一个任务组包含1/2/3
然后在任务组完成的回调中再并发出三个任务4/5/6
而且还无法控制线程的阻塞、除非在想要阻塞的地方加入最后一个同步任务
想想就很麻烦……

原文移步这里

你可能感兴趣的:(GCD篇(2))