多线程之GCD

1. GCD 初识

1.1 GCD的介绍

  • 全称是 Grand Central Dispatch,也简称 Dispatch;
  • 纯 C 语言,提供了非常多强大的函数;
  • GCD 是苹果公司为多核的并行运算提出的解决方案;
  • GCD 会自动充分利用设备的多核(比如双核、四核);
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
  • 开发者只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。

1.2 GCD中有2个核心概念

  • 任务:执行什么操作,任务使用 block 封装
  • 队列:用来存放(调度)任务

GCD 的任务
GCD 中的任务有两种封装:dispatch_block_tdispatch_function_t
dispatch_block_t(常用)
提交给指定队列的 block,无参无返回值。

typedef void (^dispatch_block_t)(void);

dispatch_function_t
提交给指定队列的 function,void(*)()类型的函数指针。

typedef void (*dispatch_function_t)(void *);

1.3 GCD的使用就2个步骤

  1. 定制任务:确定想做的事情
  2. 创建/获取队列:创建/获取一个并发/串行队列;
  3. 将任务添加到队列中(同时指定任务的执行方式):
    GCD会自动将队列中的任务取出,放到对应的线程中执行
    任务的取出遵循队列的FIFO原则:先进先出,后进后出

GCD 中,要执行队列中的任务时,会自动开启一个线程,当任务执行完,线程不会立刻销毁,而是放到了线程池中。如果接下来还要执行任务的话就从线程池中取出线程,这样节省了创建线程所需要的时间。但如果一段时间内没有执行任务的话,该线程就会被销毁,再执行任务就会创建新的线程。

1.4 同步和异步的区别

  • 同步

1.必须等待当前语句执行完毕,才会执行下一条语句
2.不会开启线程
3.在当前线程执行 block 的任务

  • 异步

1.不用等待当前语句执行完毕,就可以执行下一条语句
2.在新的线程中执行任务,具备开启新线程的能力。

注意:具备开启新线程的能力,不代表一定能开启新线程。如在主队列异步执行,不会开启新线程,因为主队列的任务在主线程上执行

1.5 执行任务的函数

  • 同步
    dispatch_sync
    提交一个 block 对象到指定队列以同步执行,并在该 block 完成执行后返回(阻塞)。
/*!
 * @param queue
 * 提交block的队列,这个队列会被系统retain直到block运行完成;
 * 此参数不能为空(NULL)
 *
 * @param block
 * 要执行的block,block会被自动copy与release;
 * 该block没有返回值,也没有参数;
 * 此参数不能为空(NULL)
 */
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
--------------------------------------------------------------------------------------------
- (void)test
{
    dispatch_queue_t queue = dispatch_queue_create("com.junteng.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"0");
    dispatch_sync(queue, ^{
        NSLog(@"1");
    });
    dispatch_sync(queue, ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

dispatch_sync_f
提交一个 function 到指定队列以同步执行,并在该 function 完成执行后返回(阻塞)。

/*!
 * @param queue
 * 提交函数的队列,这个队列会被系统retain直到block运行完成;
 * 此参数不能为空(NULL)
 *
 * @param context
 * 传递给函数的参数,即work的参数
 * 
 * @param work
 * 要执行的函数;
 * 此参数不能为空(NULL)
 */
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
--------------------------------------------------------------------------------------
- (void)test
{
    dispatch_queue_t queue = dispatch_queue_create("com.china.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"0");
    dispatch_sync_f(queue, NULL, testFunc);
    NSLog(@"2");
}

void testFunc() {
    NSLog(@"1");
}
  • 异步
    dispatch_async
    提交一个 block 对象到指定队列以异步执行,并直接返回(不会阻塞)
    dispatch_async_f
    道理同 dispatch_sync_f,不再赘述。

1.6 队列 - 负责调度任务

  • 串行队列(DISPATCH _QUEUE _SERIAL)

1.一次只能"调度"一个任务
2.dispatch_queue_create("abc", NULL);

  • 并发队列(DISPATCH _QUEUE _CONCURRENT)

1.一次可以"调度"多个任务,所有任务不按照顺序执行
2.dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);

常用队列:

  • 主队列(dispatch_queue_main_t)

1.专门用来在主线程上调度任务的队列
2.不会开启线程
3.在主线程空闲时才会调度队列中的任务在主线程执行
4.dispatch_get_main_queue()

  • 全局并发队列(dispatch_queue_global_t)

一种特殊的并发队列,可以指定服务质量(服务质量有助于确定队列执行的任务的优先级)。

image.png

1.8 GCD 各种队列的执行效果

  • 开不开线程由执行任务的函数决定
    异步开,异步是多线程的代名词
    同步不开
  • (异步)开几条线程由队列决定
    ○ 串行队列开一条线程
    ○ 并发队列开多条线程,具体能开的线程数量由底层线程池决定
    iOS 8.0 之后,GCD 能够开启非常多的线程
    iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
image.png

1.9 死锁

1.9.1 死锁的四大条件

1. 互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2. 占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3. 不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4. 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

1.9.2 GCD 中的死锁

  • 死锁情况:
    使用 dispatch_sync 函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁)。
  • 死锁原因:
    队列引起的循环等待。

2. GCD进阶

2.1 GCD 队列的服务质量与优先级

服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

iOS 8.0(新增)

/服务质量类,用于确定队列执行的任务的优先级。/
typedef qos_class_t dispatch_qos_class_t;

QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
QOS_CLASS_DEFAULT 0x15,  默认(用来底层重置队列使用的,不是给程序员用的)
QOS_CLASS_UTILITY 0x11,  实用工具(专门用来处理耗时操作!)
QOS_CLASS_BACKGROUND 0x09, 后台
QOS_CLASS_UNSPECIFIED 0x00,  未指定,可以和iOS 7.0 适配

iOS 7.0

DISPATCH_QUEUE_PRIORITY_HIGH 2  高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认优先级
DISPATCH_QUEUE_PRIORITY_LOW (-2)  低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0),为未来保留使用的,应该永远传入0

2.2 GCD 队列任务间依赖关系

dispatch_set_target_queue
设置队列的 QoS 或者优先级和另一个队列一样,除此之外,还能够创建队列的层次体系。当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的 target 指向新创建的队列即可,比如:

image.png

    dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_async(queue1, ^{
        NSLog(@"执行任务1,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
        sleep(1);
    });
    dispatch_async(queue2, ^{
        NSLog(@"执行任务2,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
        sleep(1);
    });
    dispatch_async(queue2, ^{
        NSLog(@"执行任务3,%s",dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
        sleep(1);
    });

2020-04-28 22:32:12.386289+0800 LockTest[9191:377005] 执行任务1,queue1
2020-04-28 22:32:14.390151+0800 LockTest[9191:377005] 执行任务2,queue2

注意点: 避免相互依赖,如将队列 A 的目标队列设置为队列 B,并将队列 B 的目标队列设置为队列 A。

2.3 Dispatch Block

前面说过,GCD 中的任务有两种封装:dispatch_block_tdispatch_function_t,且 dispatch_block_t 比较常用。

dispatch_block_create

创建一个 dispatch_block_t 对象。

dispatch_block_create_with_qos_class

创建一个带有 QoS 的 block,指定 block 的优先级

dispatch_block_notify

在被观察块 block 执行完毕之后,立即将通知块 block 提交到指定队列。

dispatch_block_wait

同步等待,直到指定的 block 执行完成或指定的超时时间结束为止才返回;
设置等待时间 DISPATCH_TIME_NOW 会立刻返回,
设置 DISPATCH_TIME_FOREVER会无限期等待指定的 block 执行完成才返回。

dispatch_block_cancel

异步取消指定的 block,正在执行的 block 不会被取消。

dispatch_block_testcancel

测试指定的 block 是否被取消。返回非0代表已被取消;返回0代表没有取消。

2.4 Dispatch Group 队列组

2.4.1 队列组的使用
GCD 队列组,又称“调度组”,实现所有任务执行完成后有一个统一的回调。
有时候我们需要在多个异步任务都执行完毕以后再继续执行其他任务,这时候就可以使用队列组。

dispatch_group_create

创建一个队列组

dispatch_group_async

异步执行一个 block,并与指定的队列组关联。

dispatch_group_notify

等待先前 dispatch_group_async 添加的 block 都 执行完毕以后,将 dispatch_group_notify 中的 block 提交到指定队列。

dispatch_group_wait

同步等待先前 dispatch_group_async 添加的 block 都执行完毕或指定的超时时间结束为止才返回。

例如:异步下载歌曲,等所有歌曲都下载完毕以后,转到主线程提示用户。

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_async(group, queue, ^{
        NSLog(@"%@,下载歌曲1",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"%@,下载歌曲2",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"%@,下载歌曲3",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"%@,下载完毕",[NSThread currentThread]);
    });

2.4.2 队列组的原理
真正实现统一回调的操作:

void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
-------------------------------------------------------
    dispatch_group_async(group, queue, ^{ 
    }); 
    //等价于
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        dispatch_group_leave(group);
    });
2.5 Dispatch Once 一次性执行

2.5.1 一次性执行的使用
dispatch_once
使用以下dispatch_once代码块实现一次性执行(在当前线程执行)

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        <#code to be executed once#>
    });
---------------------------------
- (void)test
{
    for (int i = 0; i < 100; i++) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    }
}
// <NSThread: 0x600002c4e1c0>{number = 1, name = main}

dispatch_once_f
道理同 dispatch_sync_f,不再赘述。

2.5.2 一次性执行的原理
判断一个全局静态变量的值,默认是 0,执行完dispatch_once后设置为 -1。
dispatch_once内部会判断这个变量的值,如果是 0 才执行。dispatch_once常被用于创建单例、swizzeld method等功能。

2.5.3 实现单例线程安全
使用dispatch_once可以让单例线程安全,并且比加锁的效率更高。

2.6 Dispatch After 延迟执行

dispatch_time
创建一个 dispatch_time_t 对象,通常与 dispatch_after 函数配合使用。

dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

dispatch_after
延迟对应时间后,异步添加 block 到指定的 queue。

注意点: dispatch_after函数并不是延迟对应时间后立即执行block中的任务,而是在指定时间后将任务加到指定队列中,考虑到队列阻塞等情况,这个任务延迟执行的时间是不准确的。

/*!
 * @param when   延迟多长时间(精确到纳秒)
 * @param queue  队列
 * @param block  任务
 */
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
--------------------------------------------------------------------------------
- (void)test
{
    //指定2s后,将任务加到队列中
    NSLog(@"%@",[NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
}

dispatch_after_f
道理同 dispatch_sync_f,不再赘述。

2.7 Dispatch Barrier 栅栏函数

2.7.1 Dispatch Barrier 简介

Dispatch Barrier:在并发调度队列中执行的任务的同步点。
使用栅栏来同步调度队列中一个或多个任务的执行。在向并发调度队列添加栅栏时,该队列会延迟栅栏任务(以及栅栏之后提交的所有任务)的执行,直到所有先前提交的任务都执行完成为止。在完成先前的任务后,队列将自己执行栅栏任务。栅栏任务执行完毕后,队列将恢复其正常执行行为。

Dispatch Barrier 栅栏函数:

同步栅栏函数

  • dispatch_barrier_sync:提交一个栅栏 block 以同步执行,并等待该 block 执行完,会阻塞当前线程。;
  • dispatch_barrier_sync_f:提交一个栅栏 function 以同步执行,并等待该 function 执行完。
    异步栅栏函数
  • dispatch_barrier_async:提交一个栅栏 block 以异步执行,并直接返回;
  • dispatch_barrier_async_f:提交一个栅栏 function 以异步执行,并直接返回。

注意点:

  • dispatch_barrier_(a)sync 函数传入的的队列必须是自己手动创建的并发队列,如果传入的是全局并发队列或者串行队列,那么这个函数是没有栅栏的效果的,效果等同于dispatch_(a)sync函数。
  • 只能栅栏 dispatch_barrier_(a)sync 函数中传入的queue。
Ø 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
Ø 适合于大规模的 I/O 操作

准备工作
@interface ViewController (){
    //加载照片队列
  dispatch_queue_t  _photoQueue;
}
@property (nonatomic, strong) NSMutableArray *photoList;
@end

 

@implementation ViewController
- (NSMutableArray *)photoList {
   if (_photoList == nil) {    
  _photoList = [[NSMutableArray alloc] init];
    }
    return_photoList;
}
提示:NSMutableArray 是非线程安全的。 

viewDidLoad 
- (void)viewDidLoad {
    [superviewDidLoad];
    _photoQueue= dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);

   for (int i =0; i < 330;++i) {    
  [self loadPhotos:i];
    }
    NSLog(@"come here");
} 

模拟下载照片并在完成后添加到数组

- (void)loadPhotos:(int)index
{
    dispatch_async(_photoQueue, ^{   
  NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];
     NSString *path = [[NSBundlemainBundle] pathForResource:fileNameofType:nil];   
     UIImage *image = [UIImage imageWithContentsOfFile:path];

      NSLog(@"下载照片%@,%d", fileName,index);
       [self.photoList addObject:image];
    });

}

由于 NSMutableArray 是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况。 

使用dispatch_barrier_async修复崩溃情况
- (void)loadPhotos:(int)index
{
    dispatch_async(_photoQueue, ^{  
    NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];     
    NSString *path = [[NSBundle mainBundle] pathForResource:fileNameofType:nil];     
    UIImage *image = [UIImage imageWithContentsOfFile:path];
      NSLog(@"下载照片%@,%d", fileName,index);

        dispatch_barrier_async(_photoQueue, ^{
          NSLog(@"添加图片 %@,%@", fileName,[NSThread currentThread]);     
      [self.photoList addObject:image];   
  });
    });
}

使用 dispatch_barrier_async 添加的 block
会在之前添加的 block 全部运行结束之后,统一在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

Barrier工作示意图


Snip20160427_7.png
2.8 Dispatch Semaphore 信号量

GCD 信号量dispatch_semaphore可以用来控制最大并发数量,可以用来实现 iOS 的线程同步方案。

  • 信号量的初始值,可以用来控制线程并发访问的最大数量;
  • 信号量的初始值为1,代表同时只允许 1 条线程访问资源,保证线程同步

Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。 Dispatch Semaphore 提供了三个方法:

  • dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
    //信号量的初始值
    int value = 1;
    //创建信号量
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
    //如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0)
    //如果信号量的值>0,就-1,然后继续往下执行代码
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //让信号量的值+1
    dispatch_semaphore_signal(semaphore);

Dispatch Semaphore 在实际开发中主要用于:

  • 保持线程同步,将异步执行任务转换为同步执行任务
  • 保证线程安全,为线程加锁
2.9 Dispatch Apply 多次执行

dispatch_apply

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的方法 dispatch_apply。dispatch_apply 按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

注意点:

  • 该函数会等待 block 都执行完毕才会返回,所以是同步的,会阻塞
  • 如果指定的队列是全局并发队列dispatch_get_global_queue,则这些 block 可以并发执行,这里需要注意可重入性; (可重入性相关的文章推荐:可重入与线程安全
  • 如果指定的队列是手动创建的并发队列,在有些情况下不会并发执行,所以建议使用全局并发队列dispatch_get_global_queue
  • 当使用串行队列时,不会开启子线程,block 在主线程按串行执行;
  • 当使用并发队列时,不一定会开启子线程,block 不一定都在子线程执行,也可能都在主线程执行,取决于任务的耗时程度。
/**
 * 快速迭代方法 dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

dispatch_apply_f
道理同 dispatch_sync_f,不再赘述。

2.10 Dispatch Source

2.10.1 dispatch_source
来自文章:关于GCD开发的一些事儿

dispatch 框架提供一套接口用于监听系统底层对象(如文件描述符、Mach 端口、信号量等),当这些对象有事件产生时会自动把事件的处理 block 函数提交到 dispatch 队列中执行,这套接口就是 Dispatch Source API,Dispatch Source 其实就是对 kqueue 功能的封装,可以去查看 dispatch_source 的 c 源码实现(什么是 kqueue?Google,什么是 Mach 端口? Google Again),Dispatch Source 主要处理以下几种事件:

DISPATCH_SOURCE_TYPE_DATA_ADD        变量增加
DISPATCH_SOURCE_TYPE_DATA_OR         变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND       Mach端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV       Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE  内存压力情况变化
DISPATCH_SOURCE_TYPE_PROC            与进程相关的事件
DISPATCH_SOURCE_TYPE_READ            可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL          接收信号
DISPATCH_SOURCE_TYPE_TIMER           定时器事件
DISPATCH_SOURCE_TYPE_VNODE           文件系统变更
DISPATCH_SOURCE_TYPE_WRITE           可写入文件映像

当有事件发生时,dispatch source 自动将一个 block 放入一个 dispatch queue 执行。

dispatch_source_create
创建一个 dispatch source,需要指定事件源的类型,handler 的执行队列,dispatch source 创建完之后将处于挂起状态。此时 dispatch source 会接收事件,但是不会进行处理,你需要设置事件处理的 handler,并执行额外的配置;同时为了防止事件堆积到 dispatch queue 中,dispatch source 还会对事件进行合并,如果新事件在上一个事件处理 handler 执行之前到达,dispatch source 会根据事件的类型替换或者合并新旧事件。

dispatch_source_set_event_handler
给指定的 dispatch source 设置事件发生的处理 handler

dispatch_source_set_cancel_handler
给指定的 dispatch source 设置一个取消处理 handler,取消处理 handler 会在 dispatch soruce 释放之前做些清理工作,比如关闭文件描述符:

    dispatch_source_set_cancel_handler(mySource, ^{ 
        close(fd); //关闭文件秒速符 
    });

dispatch_source_cancel
异步地关闭 dispatch source,这样后续的事件发生时不去调用对应的事件处理 handler,但已经在执行的 handler 不会被取消。

2.10.2 GCD 定时器

NSTimer 和 CADisplayLink 定时器不准时的问题,解决办法就是使用 GCD 定时器。GCD 的定时器是直接跟系统内核挂钩的,而且它不依赖于RunLoop,所以它非常的准时。

    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
    
    //创建定时器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //设置时间(start:几s后开始执行; interval:时间间隔)
    uint64_t start = 2.0; //2s后开始执行
    uint64_t interval = 1.0; //每隔1s执行
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
    //设置回调
    dispatch_source_set_event_handler(timer, ^{
       NSLog(@"%@",[NSThread currentThread]);
    });
    //启动定时器
    dispatch_resume(timer);
    NSLog(@"%@",[NSThread currentThread]);
    
    self.timer = timer;

2.11 dispatch_queue_set_specific & dispatch_get_specific
这两个 API 类似于objc_setAssociatedObjectobjc_getAssociatedObject,FMDB 里就用到这个来防止死锁,来看看 FMDB 的部分源码:

    static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
    //创建一个串行队列来执行数据库的所有操作
    _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

    //通过key标示队列,设置context为self
    dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

当要执行数据库操作时,如果在 queue 里面的 block 执行过程中,又调用了 indatabase 方法,需要检查是不是同一个 queue,因为同一个 queue 的话会产生死锁情况

- (void)inDatabase:(void (^)(FMDatabase *db))block {
    FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
    assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}

3. GCD 源码

libDispatch

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,323评论 0 2
  • GCD介绍 Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法...
    巫师学徒阅读 250评论 0 0
  • Runloop 什么是RunloopRunloop就是消息循环,每一个线程内部都有一个消息循坏。只有主线程的消息循...
    冷漠叻荭颜阅读 8,537评论 4 27
  • 无论心里如何觉得自己小,无论别人看着觉得你多么年轻,都无法抗衡岁月的流逝。如果说有不老童颜,冻龄美人,那么我想采访...
    雨荷_6fdb阅读 336评论 7 8
  • 上海诗人早安语录:人在生活中,会遇到很多问题,人生,就是一场邂逅问题的修行。不论是走荆棘的小路,还是宽阔的草原,问...
    刘时光代阅读 618评论 3 50