GCD

12/18/2022

# 任务和队列

# 任务

任务就是需要执行的操作,是GCD中放在block中在线程中执行的那段代码。
任务的执行的方式有同步执行和异步执行两中执行方式。

  • 同步执行:dispatch_sync 阻塞当前线程,直到当前添加到队列中的任务执行结束,才继续往下执行。
  • 异步执行:dispatch_async 不阻塞当前线程

# 队列

在GCD里面队列是指执行任务的等待队列,是用来存放任务的。队列的结构按照FIFO(先进先出)的原则进行执行。GCD的队列分为串行队列和并发队列两种

  • 串行队列:只开启一个线程,每次只能有一个任务执行,等待执行完毕后才会执行下一个任务
  • 并发队列:可以让多个任务同时执行,也就是开启多个线程,让多个任务同时执行。

注意: dispatch_async异步调用情况 串行队列:是overcommit的,创建队列会创建1个新的线程 并发队列:是非overcommit的,不一定会创建新线程,会从线程池的64个线程中获取并使用。所以避免在循环或者递归中新建串行队列。串行队列的会创建新线程,最多512个(不算主线程,number从4开始到515)

# 任务和队列的组合

同步执行 异步执行
串行队列 同步任务不会开启新的线程,任务串行执行 异步任务有开启新的线程,任务串行执行
并行队列 同步任务不会开启新的线程,虽然任务在并发队列中,但是系统只默认开启了一个主线程,没有开启子线程,所以任务串行执行 异步任务不一定有开启新的线程,会从线程池的64个线程中获取并使用
主队列 主队列是一种串行队列,任务在主线程中串行执行,将同步任务添加到主队列中会造成追加的同步任务和主线程中的任务相互等待阻塞主线程,导致死锁 主队列是一种串行队列,任务在主线程中串行执行,即使是追加的异步任务也不会开启新的线程,任务串行执行。

# 线程池

dispatch_queue 是GCD实现的一种线程池技术。不管是自定义队列、全局队列还是主队列,最终都直接或者间接的依赖12个root队列来执行任务调度。如果按照label来计算总共16个,除上面的12个,就是com.apple.main-thread,还有两个内部管理队列com.apple.libdispatch-manager和com.apple.root.libdispatch-manager,以及runloop的运行队列

一、 全局线程池

  1. 主线程池:main queue
  2. 其他线程池:
  • DISPATCH_QUEUE_PRIORITY_HIGH
  • DISPATCH_QUEUE_PRIORITY_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND
  1. 一个池最多支持几个线程同时工作?
    64个

二、自定义线程池

自己创建的queue跟系统的4个全局池模式一样,也是最多64个线程

# 函数方法

# dispatch_group

调度组简单来说就是把异步执行的任务进行分组,等待所有的分组任务都执行完毕后再回到指定的线程执行任务
dispatch_group_a/sync
dispatch_group可以将很多队列添加到一个组,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。

  1. dispatch_group是一个初始值为LONG_MAX到信号量,group中的任务完成是判断value是否恢复成初始值
  2. dispatch_group_enter和dispatch_group_leave必须成对使用并支持嵌套
  3. 如果dispatch_group_enter比dispatch_group_leave多,由于value不等于初始值(dsema_orig)不会走到唤醒逻辑,dispatch_group_notify中的任务无法执行或者dispatch_group_wait收不到信号而卡住线程。如果是dispatch_group_leave多,则会引起崩溃。

# dispatch_barrier

有的时候我们需要异步执行两组操作,等待第一组执行完成后才回去执行第二组操作,这个时候栅栏方法就起作用了.
简单来说dispatch_barrier_async或dispatch_barrier_sync将异步任务分成了两个组,执行完第一组后,再执行自己,然后执行队列中剩余的任务

dispatch_barrier允许在一个并发队列中创建一个同步点。当在并发队列中遇到barrier,它会延迟执行barrier的block,等待所有在barrier之前提交的blocks执行结束。这时,barrier block自己开始执行。之后,队列继续正常执行操作。
这里所指的并发队列应该是自己通过dispatch_queue_create函数创建的。如果你传的是一个串行队列或者全局并发队列,这个函数就等同于dispatch_a/sync函数。

# dispatch_semaphore

GCD中的信号量是持有计数的信号。当信号量小于0时就会一直等待即阻塞所在线程,否则就可以正常执行。信号量可以保持线程的同步,将异步执行任务转换成同步任务执行, 同时保持线程的安全。

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。

# dispatch_after

dispatch_after 方法并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中

# dispatch_once

GCD提供了只执行一次的方法dispatch_once,这个方法在我们创建单例的时候回经常用到。dispatch_once方法可以保证一段代码在程序运行过程中只被调用一次,而且在多线程环境下可以保证线程安全。

+ (instancetype)shareInstance{
    static XXX *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [XXX alloc]init];
    });
    return instance;
}
1
2
3
4
5
6
7
8

# dispatch_source

调度源

# 死锁场景

死锁主要出现在同步执行 + 串行队列里

# 场景一:同步+主队列

[self task1];
dispatch_sync(dispatch_get_main_queue(), ^{
    [self task2];
    NSLog(@"同步线程执行主队列任务");
});
[self task3];
1
2
3
4
5
6
  1. 执行task1
  2. 阻塞同步线程,把task2加入到主队列的队尾
  3. task3需要等待task2执行完成后执行,但是此时task2又排在task3后面,所以造成了死锁

# 场景二:异步串行队列嵌套同步串行队列

dispatch_queue_t myQueue = dispatch_queue_create("com.bg.sQueue", DISPATCH_QUEUE_SERIAL);
[self task1];
dispatch_async(myQueue, ^{
   [self task2];
   dispatch_sync(myQueue, ^{
   [self task3];
   });
   [self task4];
});
[self task5];
1
2
3
4
5
6
7
8
9
10
  1. 执行task1
  2. 执行task5
  3. 执行task2
  4. 阻塞同步线程,把task3加入到队列myQueue的队尾
  5. task4需要等待task3执行完成后执行,但是此时task3又排在task4后面,所以造成了死锁

# 场景三:信号量阻塞主线程

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
    dispatch_semaphore_signal(sem);
    NSLog(@"the sem +1");
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"the sem -1");
1
2
3
4
5
6
7

主线程中dispatch_semaphore_wait一直等着dispatch_semaphore_signal改变信号量(+1操作),但是dispatch_semaphore_wait却阻塞了主线程导致dispatch_semaphore_signal无法执行,从而造成了死锁。

Last Updated: 10/25/2024, 6:55:06 AM