初探GCD

GCD简介

GCD是一种简洁的多线程编程技术,通过配合Block让我们平时在需要用到异步时可以简单地使用,而不需要考虑线程的建立,调度等问题。XNU内核通过上下文切换来完成任务的调度(线程就是任务调度的抽象),不过这也带来了一些问题,比如说数据竞争,死锁,消耗内存等,为了解决这些问题,编程会变得很复杂,而GCD大大地简化了多线程编程的难度。

GCD技能树

Dispatch Queue

首先要申明一点,queue不等于线程,多个queue也不代表了多个线程,一个queue也可能对应了多个线程,这里的queue其实是一个存放task的队列,遵循先进先出原则。

Queue可以分两种,一种是serial,一种concurrent,一个serial队列只有一个线程执行任务,也就是说这是需要等待正在处理的任务结束了CGD才会分发第二个任务进行处理,而concurrent则不一样,它对应了很多个线程(视具体处理器,程序运行情况而定)。

dispatch_queue_create

dispatch_queue_t tempQueue = dispatch_queue_create("com.LY.Test", NULL);

通过这种方法可以创建一个队列,通过改变第二个参数,可以指定是serial还是concurrent模式(DISPATCH_QUEUE_CONCURRENT)。还有一点比较好的地方就是ARC后编译器已经可以帮我们管理内存了,也就是把GCD对象看成OC对象了。

虽然我们可以创建多个serial队列来模拟concurrent队列,不过这会消耗很大的性能,因为每一个serial都会阐述一个线程,必然带来了大量的内存消耗。而concurrent队列则不一样,由系统帮我们觉得是否开辟新线程,还是使用原有的线程。

Main/Global Dispatch Queue

dispatch_queue_t tempQueue = dispatch_get_main_queue();
dispatch_queue_t tempQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

第一个队列从名字就能看出来对应了主线程,是一个serial队列。第二个则是一个concurrent队列有四种优先级(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_PRIORITY_DEFAULT,DISPATCH_QUEUE_PRIORITY_LOW,DISPATCH_QUEUE_PRIORITY_BACKGROUND),其实这也只能说是一个大概的优先级区别,并不是说后台队列就只能用在后台。

dispatch_set_target_queue

系统级的队列不需要我们设立优先级,当我们自己创建队列时,默认都是DISPATCH_QUEUE_PRIORITY_DEFAULT级别的。

dispatch_queue_t tempQueue = dispatch_queue_create("com.LY.test", NULL);
dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(tempQueue, bgQueue);

这个函数还有一个作用,能够建立“依赖”,这里的依赖要打个引号,其实只是相当于把多个serial队列的target设为统一serial队列,那么能够起到本来是并发执行的各个serial队列,现在每次只能有一个任务在执行中。

dispatch_queue_t tempQueue = dispatch_queue_create("com.LY.test", NULL);
dispatch_queue_t temp2Queue = dispatch_queue_create("com.LY.test1", NULL);
dispatch_queue_t temp1Queue = dispatch_queue_create("com.LY.test2", NULL);
dispatch_set_target_queue(temp1Queue, tempQueue);
dispatch_set_target_queue(temp2Queue, tempQueue);

dispatch_async(temp1Queue, ^{
         NSLog(@"1");
        sleep(3);

    });
dispatch_async(temp2Queue, ^{
        NSLog(@"2");
    });
dispatch_async(temp1Queue, ^{
            NSLog(@"3");
        });

如果没有设置target的话,这里的输出是1 2 3;加了同一个target后就变成1 3 2了。

Dispatch 组合技

Dispatch 同步/异步

dispatch_sync
dispatch_async
dispatch_barrier_sync
dispatch_barrier_async

这里有一个点很重要,同步/异步,阻塞/非阻塞其实是两个概念,一个关注的消息通信机制,一个关注的是程序在等待时的状态。

Dispatch Group

这个用处就多了,最基本的用法,先执行一系列的任务,等这些都执行完了,再执行一个特殊的任务。如果用普通的queue来实现会增加很多不必要的操作。这里用gruop来实现就很简单了。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"3");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"main");
});

这里的执行结果肯定的是main是最后一个打印的。

  • 这个可以阻塞当前线程直到group中得任务全部执行结束。dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  • 这个可以检测当前group是否已经全部执行完。long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);

通过调节时间可以完成各种花样。

Dispatch Barrier

有时候我们想在一个并发队列中存在一个任务以serial的形式执行,这时候我们就需要用到barrier

dispatch_queue_t queue = dispatch_queue_create("com.LY.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"1");
});
dispatch_async(queue, ^{
    NSLog(@"1");
});
dispatch_async(queue, ^{
    NSLog(@"1");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"barrier");
});
dispatch_async(queue, ^{
    NSLog(@"2");
});
dispatch_async(queue, ^{
    NSLog(@"2");
});

不管前面三个什么时候结束,可以肯定的是2 2肯定是在1 1 1 barrier后面出现。

Dispatch Semaphore

这个可以更加精细化的处理并发时的数据不一致问题,从名字上能够看出来这是通过信号来处理的,只有在信号大于0的时候不需要等待。

  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *arr = [NSMutableArray array];

for (int i=0; i < 10000; i++) {
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        [arr addObject:@(i)];

        dispatch_semaphore_signal(semaphore);
    });
}

for (NSNumber *n in arr) {
    NSLog(@"%@", n);
}

结果是从0-9999按序输出,用法:先创建一个semaphore,然后通过dispatch_semaphore_wait来判断是否需要等待,等待多长时间,通过dispatch_semaphore_signal这个函数来给计数加1.

Dispatch小技巧

dispatch_after

这个就比较简单了,其实就是延时用dispatch_async添加到队列中,不会阻塞当前线程。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2ull * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"deley");
});

dispatch_apply

这个可以解决单次循环比较耗时的问题。配合async使用效果更加。

dispatch_queue_t queue = dispatch_queue_create("com.LY.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t t) {
    NSLog(@"%@" , @(t));
});

dispatch_suspend/resume

这个从字面上就能看出,一个是挂起队列,一个是重新开始执行,需要注意的是已经在执行的任务是无法挂起的。

GCD实现&&总结

GCD虽然源码已经放出来了,不过从中可以看出,苹果用了很多手法来反编译,全是混淆,也导致阅读难度大量增加。

总的来说,Dispatch Queue主要用到libdispatch,Libc,以及XNU内核中得workqueue。而我们作为一个普通开发所使用的大多都在libdispatch中。

大致的原理是,通过结构体和链表实现FIFO队列,然后其他函数辅助,其中Block不是直接加入队列,中间会转一层。

具体的源码解析,还有dispatch的source放到以后再说,这次主要记录一下整个dispatch的基本使用。