iOS底层(十五)-GCD(二)

503 阅读5分钟

一、前言

之前主要讲解了一些并发串行的相互搭配的使用,分别为同步串行、同步并发、异步串行、异步并发这四种。 下面就来讲解一下GCD的一些其他方面的应用

二、GCD之信号量

2.1、信号量简介

GCD信号量主要就是三个函数:

dispatch_semaphore_create(long value); // 创建信号量, 定义信号量大小
dispatch_semaphore_signal(dispatch_semaphore_t deem); // 发送信号
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待接收信号

dispatch_semaphore_create(long value); : 创建一个信号量,并且指定大小,当信号量到达0的时候,就会等待信号量的释放 dispatch_semaphore_signal(dispatch_semaphore_t deem); : 发送一个信号量,此时信号量-1 dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); : 接收一个信号量,此时信号量+1

当他们组合使用的时候,原理相当于给定一个数,执行一次任务,就消耗信号,任务完成就返回信号,信号使用完就必须等待返回信号。

2.2、使用场景

先看一段代码:

__block int v = 0;
while (v < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        v++;
    });
}
NSLog(@"%d", v);

这样去打印v的值,结果肯定是>=5的。 因为在while循环里,每一次循环,并不意味着一次异步队列的完成。所以一次while循环,可能会加进去好多次任务。为了避免这种情况,打印出v=5。这里就使用信号量来处理一下。

分析:

  1. 外部创建一个信号量,大小为1, 任务是一个个执行的
  2. 每执行完一次循环,必须等待任务执行完毕才可以继续循环

这么一分析,也就是在v++后释放信号量,while里面等待接收

dispatch_semaphore sem = dispatch_semaphore_create(1);
__block int v = 0;
while (v < 5) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        v++;
        dispatch_semaphore_signal(sem);
    });
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"%d", v);

三、GCD之栅栏函数

3.1、栅栏函数简介

dispatch_barrier_sync(queue, ^{
    NSLog(@"同步栅栏--%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
    NSLog(@"异步栅栏--%@",[NSThread currentThread]);
});

栅栏函数就是GCD封装的一些特性函数,它主要是起到一个栅栏效果(堵塞),只有当栅栏前面的任务完成后,栅栏后面的才会继续执行。同步栅栏不仅会堵塞这个队列, 还会堵塞当前线程。

3.2、使用场景

3.2.1、同步+同步栅栏

dispatch_queue_t queue = dispatch_queue_create("com.test1", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_sync(queue, ^{
    NSLog(@"2");
});
dispatch_barrier_sync(queue, ^{
    NSLog(@"栅栏");
});
dispatch_sync(queue, ^{
    NSLog(@"3");
});
NSLog(@"4");

这种情况会直接堵塞当前的线程, 结果是:

1 2 栅栏 3 4

3.2.2、异步+同步栅栏

dispatch_queue_t queue = dispatch_queue_create("com.test1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
    NSLog(@"2");
});
dispatch_barrier_sync(queue, ^{
    NSLog(@"栅栏");
});
dispatch_async(queue, ^{
    NSLog(@"3");
});
NSLog(@"4");

碰到异步函数, 系统开辟线程,主线程继续向下执行,主线程碰到栅栏,等待上一个此线程的任务执行完毕才会继续向下执行。 因为是同步栅栏,会连带主线程一起阻塞。

结果是:

1 2 栅栏 3 4

3.2.3、同步+异步栅栏

dispatch_queue_t queue = dispatch_queue_create("com.test1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_sync(queue, ^{
    NSLog(@"2");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"栅栏");
});
dispatch_sync(queue, ^{
    NSLog(@"3");
});
NSLog(@"4");

同理,因为其他任务是同步,一个跟着一个到栅栏,栅栏函数堵塞当前任务队列,此时之前任务已经完成,继续向下。

结果是:

1 2 栅栏 3 4

3.2.3、异步+异步栅栏

dispatch_queue_t queue = dispatch_queue_create("com.test1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
    NSLog(@"2");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"栅栏");
});
dispatch_async(queue, ^{
    NSLog(@"3");
});
NSLog(@"4");

因为其他任务为异步, 所以会先执行主线程的任务1 4, 执行第一个异步任务,碰到栅栏,等待第一个任务执行完毕,栅栏执行完毕,执行第二个异步任务

结果是:

1 4 2 栅栏 3

3.2.3、其他+栅栏

在官方文档中有提到过,无论是异步还是同步栅栏函数,当 dispatch_get_global_queue 遇到栅栏函数, 栅栏函数是不会有效果的。

所以在使用栅栏函数,一定要使用自定义的队列。

与dispatch_get_main_queue()一起使用, 程序会进入死锁状态!

在日常开发中,尽量的使用异步栅栏函数添加到自定义的队列。 还要注意,必须是同一个队列,栅栏函数才会生效,例如AFN的网络请求是内部自定义的队列,此时栅栏函数是没有效果的。

四、GCD之调度组

4.1、调度组简介

调度组也主要是三个函数:

dispatch_group_create(); // 创建调度组
dispatch_group_async(dispatch_group_t _Nonnull group>, dispatch_queue_t _Nonnull queue>, ^(void)block);//异步调度
dispatch_group_notify(dispatch_group_t _Nonnull group, dispatch_queue_t _Nonnull queue, (void)block);//通知调度

它可以将任务全部都放进一个调度组里完成,等待这些调度任务完成之后,会通知调度完成。

4.2、调度组简单使用

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_group_async(group, queue, ^{
    NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"3");
});

此时打印结果会是1 2 3, 并且可以发现,它可以在不同的队列里去使用调动组,效果是一样的。

调度组的原理实际上就是将一个任务,通过dispatch_group_enter()进组,通过dispatch_group_leave()出组。它们两个都是成对出现的,先进后出。

五、GCD之dispatch_source

5.1、dispatch_source简介

dispatch_source 有一个定义好的句柄,保存着一个block代码块,经过dispatch_source_merge_data后传递数据。 最常见就是使用它做定时器

dispatch_source的相关函数:

dispatch_source_create: 创建源
dispatch_source_set_event_handle: 设置源事件回调
dispatch_source_merge_data:源事件设置数据
dispatch_source_get_data:获取源事件数据
dispatch_resume:继续
dispatch_suspend:挂起

5.1、dispatch_source简单使用

@property (nonatomic, strong) dispatch_source_t source;

@implementation ViewController
- (void)viewDidLoad {

    self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, 0), 3 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(source, ^{
        //定时器触发时执行
        NSLog(@"timer响应了");
    });
    //启动timer
    dispatch_resume(source);
}
@end

Dispatch_Source使用最多的就是用来实现定时器,source创建后默认是暂停状态,需要手动调用dispatch_resume启动定时器。

在其他UI操作下调用dispatch_source_merge_data()即可发送数据。

dispatch_after只是封装调用了dispatch_source定时器,然后在回调函数中执行定义的block。