iOS GCD学习

358 阅读7分钟

GCG队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的后端进入插入操作,在表的前端进行删除操作,即遵循FIFO原则。

GCD中的队列(Dispatch Queue)就是指用来执行任务的等待队列,当我们添加任务到队列之后,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。GCD 不仅决定着你的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理。这样可以将开发者从线程管理的工作中解放出来,通过集中的管理线程,来缓解大量线程被创建的问题。

GCD队列分类

  • 串行队列 ( Serial Dispatch Queue )

    串行队列(也称为私有调度队列)按照将他们添加到队列顺序一次执行一个任务。当前正在执行的任务在由队列管理的不同线程(可能因任务而异)上运行。串行队列通常用于同步对特定资源的访问。

  • 并发队列 ( Concurrent Dispatch Queue )

    并发队列(也称为一种全局调度队列)同时执行一个或多个任务,但任务仍按其添加到队列的顺序启动。当前正在执行的任务在由调度队列管理的不同线程上运行。在任何给定点执行的任务的确切数量是可变的,取决于系统条件。

在我们平时的开发中,还有2种我们最常见的,也是使用频率最高的队列:

  • 主队列 ( Main Dispatch Queue )(串行队列的一种)

    主队列是一个全局可用的串行队列,它在应用程序的主线程上执行任务。此队列与应用程序的Runloop一起工作,将有序任务的执行与附加到Runloop的其他事件源的执行交错。因为它在应用程序的主线程上运行,所以主队列通常用作应用程序的关键同步点。

    主队列下的任务不管是异步任务还是同步任务都不会开辟线程,任务只会在主线程顺序执行

  • 全局并发队列 ( Global Dispatch Queue )(并发队列的一种)

    全局并发队列本质上是一个并发队列,有系统提供,方便编程,可以不用创建就可以直接使用

GCD任务

任务就是你要在线程中执行的代码,在GCD中是用Block来定义任务的,是用起来非常灵活便捷。

GCD任务分类

  • 同步执行

    同步执行就是指使用 dispatch_sync方法将任务同步的添加到队列里,在添加的任务执行结束之前,当前线程会被阻塞,然后会一直等待,直到任务完成。

    dispatch_sync添加的任务只能在当前线程执行,不具备开启新线程的能力

  • 异步执行

    异步执行就是指使用dispatch_async方法将任务异步的添加到队列里,它不需要等待任务执行结束,不需要做任何等待就能继续执行任务

    dispatch_async添加的任务可以在新的线程中执行任务,具备开启新线程的能力但并不一定会开启新线程

死锁

使用同步方式(dispatch_sync)提交一个任务到一个串行队列,如果提交这个任务的操作所处的线程和当前处于的串行队列是在同一线程,就会引起死锁。

6种组合使用总结

总结串行队列并发队列主队列
同步添加(sync)不开辟新线程,在当前线程中串行执行任务不开辟新线程,在当前线程中串行执行任务死锁
异步添加(async)开辟新线程(1条),串行执行任务开辟新线程(1/n条),并发执行任务不开辟新线程,在主线程中顺序执行

dispatch_barrier

GCD中提供了Dispatch_barrier系统的API,俗称栅栏,使用dispatch_barrier_sync()或者dispatch_barrier_async()入队的block,会等到所有的之前入队的block执行完成后才开始执行。除此之外,在barrier block后面入队的所有的block,会等到到barrier block本身已经执行完成之后才继续执行。

dispatch_barrier_async与dispatch_barrier_sync的对比

barrier_asyncbarrier_sync的区别仅仅在于,barrier_sync会阻塞它之后的任务的入队,必须等到barrier_sync任务执行完毕,才会把后面的异步任务添加到并发队列中,而barrier_async不需要等自身的block执行完成,就可以把后面的任务添加到队列中。

dispatch_barrier_async实现多读单写

要满足一下三点

  • 读者与读者并发
  • 读者与写着互斥
  • 写者与写者互斥
//读取
- (id)dataForKey:(NSString *)key {
    __block id data;
    //同步读取指定数据
    dispatch_sync(self.concurrentQueue, ^{
        data = [self.dict objectForKey:key];
    });
    return data;
}

//写入
- (void)setData:(id)data forKey:(NSString *)key {
    // 异步栅栏调用设置数据
    dispatch_barrier_async(self.concurrentQueue, ^{
        [self.dict setObject:data forKey:key];
    });
}

dispatch_group

如何使用dispatch_group来实现在一系列并发任务完成后做一些收尾工作的需求

添加任务到dispatch_group

添加任务有2种方式:

  • 第一种是使用dispatch_group_async添加任务到一个特定的队列
  • 第二种是人为的告诉group,我们开始了一个任务(dispatch_group_enter),或者任务结束了(dispatch_group_leave

添加监听group中任务结束时的回调

这里也有2种方式,dispatch_group_waitdispatch_group_notify

dispatch_group_wait

这种方式会阻塞当前的线程,直到group中的任务全部完成,程序才会继续往下执行。

dispatch_group_notify

这种方式是添加一个异步执行的任务作为结束任务,当group中的任务全部完成,才会执行dispatch_group_notify中添加的异步任务,这种方式不会阻塞当前线程,同时有一个单独的异步回调,代码组织性更好,使用也更新广泛一些。

dispatch_group总结

dispatch_group_asyncdispatch_group_enter都是异步添加任务,不会阻塞当前线程

dispatch_group_notify不会阻塞当前线程,dispatch_group_wait会阻塞当前线程

dispatch_group_enterdispatch_group_leave必须成对出现,否则group中的任务永远不会完成

dispatch_semaphore

dispatch_semaphore俗称信号量,也称为信号锁,在多线程编程中主要用于控制多线程下访问资源的数量,比如系统有两个资源可以使用,但同时有三个线程要访问,所以只能允许两个线程访问,第三个应当等待资源被释放后再访问,这时我们就可以使用dispatch_semaphore

semaphore的三个方法

dispatch_semaphore_create
dispatch_semaphore_create方法用于创建一个带有初始值的信号量dispatch_semaphore_t。对于这个方法的参数信号量的初始值,这里有2种情况:

  1. 信号量初始值为0时:这种情况主要用于两个线程需要协调特定事件的完成时,即线程同步。
  2. 信号量初始值为大于0时:这种情况主要用于管理有限的资源池,其中池大小等于这个值,即资源加锁。 dispatch_semaphore_wait
    dispatch_semaphore_wait这个方法主要用于等待减少信号量,每次调用这个方法,信号量的值都会减一,然后根据减一后的信号量的值的大小,来决定这个方法的使用情况,所以这个方法的使用同样也分为2种情况:
  3. 当减一后的值小于0时,这个方法会一直等待,即阻塞当前线程,直到信号量+1或者直到超时。
  4. 当减一后的值大于或等于0时,这个方法会直接返回,不会阻塞当前线程。 dispatch_semaphore_signal
    dispatch_semaphore_signal方法用于让信号量的值加一,然后直接返回。如果先前信号量的值小于0,那么这个方法还会唤醒先前等待的线程。

semaphore的释放

当前的信号值小于初始的信号值时,即信号量还在使用时,会直接调用DISPATCH_CLIENT_CRASH让APP奔溃。所以,我们在使用信号量的时候,不能在它还在使用的时候,进行赋值或者置空的操作。

摘选 摘选 摘选 摘选