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
而且还无法控制线程的阻塞、除非在想要阻塞的地方加入最后一个同步任务
想想就很麻烦……

原文移步这里

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