iOS多线程之GCD


前言

多线程编程是iOS 开发中必不可少的部分。这一篇博客主要是讲一讲GCD相关的知识。参考文章:iOS 多线程:『GCD』详尽总结


一、队列和任务

按照官方文档Dispatch的阐述:

Dispatch, also known as Grand Central Dispatch (GCD), contains language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, and tvOS.

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.

从官方文档里面可以知道GCD就是多线程的一种处理方式,跟我们上学期间学的生产者-消费者模式是一样的,要运行的Execute code 就是生产者生产的产品,因为生产者和消费者速度不一样,所以就需要有地方存放这些生产的产品,所以dispatch queues 就是这个地方,说到这就涉及到两个很重要的概念:队列(queue)和任务(task)。还有一个概念是消费者消费产品的方式:当生产者把生产的商品放到仓库,那消费者可以通过不同的方式来获取这个商品,这是GCD里面的第三个概念:执行任务的方式。

1.1 任务

任务就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。

1.2 队列

这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
在 GCD 中有两种队列:『串行队列』『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue):
    每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue):
    可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
    串行队列的代表是主队列,并发队列的代表是全局并发队列。

注意:并发队列 的并发功能只有在异步(dispatch_async)执行方法下才有效。

1.3 执行任务的方式

执行任务的方式主要有两种:

  • 同步执行(sync):
    同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
    只能在当前线程中执行任务,不具备开启新线程的能力。
  • 异步执行(async):
    异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
    可以在新的线程中执行任务,具备开启新线程的能力。

两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

1.4 队列和任务的组合方式

这里直接借用参考文章里面的内容,我也不多说了。
队列和任务的不同组合方式

二、GCD 死锁

GCD 一个最简单的死锁例子:参考文章

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    	NSLog(@"1"); // 任务1
        dispatch_sync(dispatch_get_main_queue(), ^(void){
            NSLog(@"2"); // 任务2
            NSLog(@"I am waiting for you!");
        });
        NSLog(@"3"); // 任务3
    }
    return 0;
}

Q: 这里为什么会死锁?
A: 首先执行任务1没问题,接下来程序遇到了同步线程,那么它会进入等待,等待任务2执行完,然后执行任务3。但这是主队列,是一个特殊的串行队列, 有任务来,当然会将任务加到队尾,然后遵循FIFO原则执行任务, 现在任务2就会被加到最后。如图中所示,任务3要等任务2完成,任务2又排在了任务3的后面。
死锁案例

三、GCD单例原理

参考文章:OC-底层原理-25:GCD-之-底层原理分析
单例调用原理
针对单例的底层实现,主要说明如下:

  • 【单例只执行一次的原理】:GCD单例中,有两个重要参数,onceToken 和 block,其中onceToken是静态变量,具有唯一性,在底层被封装成了dispatch_once_gate_t类型的变量l,l主要是用来获取底层原子封装性的关联,即变量v,通过v来查询任务的状态,如果此时v等于DLOCK_ONCE_DONE,说明任务已经处理过一次了,直接return

  • 【block调用时机】:如果此时任务没有执行过,则会在底层通过C++函数的比较,将任务进行加锁,即任务状态置为DLOCK_ONCE_UNLOCK,目的是为了保证当前任务执行的唯一性,防止在其他地方有多次定义。加锁之后进行block回调函数的执行,执行完成后,将当前任务解锁,将当前的任务状态置为DLOCK_ONCE_DONE,在下次进来时,就不会在执行,会直接返回

  • 【多线程影响】:如果在当前任务执行期间,有其他任务进来,会进入无限次等待,原因是当前任务已经获取了锁,进行了加锁,其他任务是无法获取锁的

四、注意点

  • 栅栏函数无法拦住全局并发队列:因为系统会使用全局并发队列,不能让开发者阻塞系统
  • 栅栏函数针对的是一个队列,对于几个队列,那么使用group相关的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值