美文网首页
笔记整理:GCD

笔记整理:GCD

作者: 双鱼子曰1987 | 来源:发表于2021-06-08 10:19 被阅读0次

一、概述

GCD是用纯C编写,效率很高;其内部自动维护一个线程池,自动管理线程的生命周期;其会利用CPU的多核特性。


image.png

二、GCD 基础

1、GCD使用的核心是队列Queue 和 任务Block;

  • 任务可分为同步任务dispatch_sync 和 异步任务dispatch_async

  • 队列就是先进先出FIFO的数据结构,可以分为串行队列 和 并行队列
    串行队列中的任务是顺序执行,即one by one;并行队列的任务并行随机执行,无顺序。

    GCD提供默认队列:主线程队列dispatch_get_main_queue() 它是主线程上的串行队列;全局队列dispatch_get_global_queue(0,0) 它是全局的并行队列。

  • 同步和异步的区别有以下两点:
    1、任务的执行是否需要阻塞当前线程,同步会阻塞,异步则不会。
    2、是否具备开启新线程的能力。
    同步任务:不管在串行或并行队列,都只能在当前线程内执行任务和等待,不具备开启新线程的能力。
    异步任务:不管在串行或并行队列,一般都会到新线程中执行任务,具备开启新线程的能力。

2、组dispatch_group —— 针对的是队列queue 和 任务task

相当于在队列中设置一个里程碑任务,这个里程碑任务 必须等到队列中其他任务都完成,才会被触发。

  • 1、创建组的方式
dispatch_group_t group =  dispatch_group_create();
  • 2、设置里程碑任务的方法
dispatch_group_notify(group, queue, ^{
    // 里程碑任务
});

注意:wait方法,其阻塞当前线程

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
执行,里程碑任务
  • 3、往group添加任务的方法
dispatch_group_async(group, queue, ^{
    // 追加任务 
});

手动控制Group的进入和退出(enter和leave必须配对)

// 进入group
dispatch_group_enter(group);
dispatch_async(queue, ^{
    // 追加任务 1

    // 当前任务退出group
    dispatch_group_leave(group);
});

3、栅栏dispatch_barrier_async —— 针对的是队列queue

以栅栏函数调用为分界点,调用之前加入队列的任务组先执行,然后执行栅栏任务,最后执行调用之后加入队列的任务组。

  • 执行顺序:任务1/任务2 ---> 栅栏任务 ---> 任务3/任务4
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        // 追加任务 1
    });

    dispatch_async(queue, ^{
        // 追加任务 2
    });
    
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
    });
    
    dispatch_async(queue, ^{
        // 追加任务 3
    });
    dispatch_async(queue, ^{
        // 追加任务 4
    });
}

4、dispatch_semaphore

主要提供三个方法
dispatch_semaphore_create(counts) 创建信号量,初始信号总量
dispatch_semaphore_signal(semp) 信号量+1
dispatch_semaphore_wait(semp, time) 信号量为0则阻塞线程,大于0则不会阻塞。

  • 使用场景1:保持线程同步,将异步任务转换为同步任务
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
dispatch_async(queue, ^{
    // 任务
    dispatch_semaphore_signal(semaphore);
});
    
// 阻塞线程,且只有执行完任务后,才会继续往下
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  • 使用场景2:保证线程安全,为线程加锁,即保证数据的准确性。

5、GCD定时器 - dispatch source

  • 定时器创建流程:source_create -> set_timer -> set_event_handler -> resume
    官方的demo示例
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue, dispatch_block_t block) {
     // 创建source
     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
       0, 0, queue);
     if (timer) {
        // 设置定时器的定时间隔、开始时间
        dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
        // 设置定时器的执行handler
        dispatch_source_set_event_handler(timer, block);
        // 唤起定时器
        dispatch_resume(timer);
     }
     return timer;
}
  • 定时器暂停(这时候不能被释放,否则EXC_BAD_INSTRUCTION 崩溃)
dispatch_suspend(_timer);
_timer = nil; // 崩溃
  • 定时器销毁
dispatch_source_cancel(_timer);
_timer = nil; // OK

6、其他常用接口

  • dispatch_once 保证代码之后执行一次。
  • dispatch_after 延迟几秒后执行任务。
  • dispatch_apply 快速迭代任务,即重复几次的执行同一个任务。
dispatch_apply(runCounts, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
});

三、队列相关配置

1、dispatch_queue_set_specific,把任意数据以键值对的形式关联到队列中

void dispatch_queue_set_specific(
    dispatch_queue_t queue, //待设置标记的队列
    const void *key, //标记的键
    void *context, //标记的值。注意,这里键和值是指针,即地址,故context中可以放任何数据,但必须手动管理context的内存
    dispatch_function_t destructor //析构函数,但所在队列内存被回收,或者context值改变时,会被调用
);
  • 使用场景:用来判断当前队列。
    使用Demo
dispatch_queue_t queueA = dispatch_queue_create("queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
 
static int kQueueSpecific;
CFStringRef queueSpecificValue = CGSTR("queueA"); 
//这里使用CoreFoundation字符串,是因为ARC不会自动管理CoreFoundation对象的内存,dispatch_queue_set_specific的第三个参数(值)需要手动管理内存
 
dispatch_queue_set_specific(
    queueA, 
    &kQueueSpecific, 
    (void *)queueSpecificValue,  //需要手动管理内存
    (dispatch_function_t)CFRelease //用CFRelease清理旧值
); //给queueA队列做标记
 
dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{ NSLog("No deadlock!"); };
    CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific); //根据键获取值
    if(retrievedValue){ //根据键找到了值,就说明包含在queueA目标队列中
        block();
    }else{ //没有包含在queueA中
        dispatch_sync(queueA, block);
    }
})

2、获取当前queue的标签 dispatch_queue_get_label(queue),一般用来判断两个队列是否相同。

// 判断当前队列是否相等
if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) {
    ...
}

3、dispatch_set_target_queue

* @discussion
 * An object's target queue is responsible for processing the object. 
 *
 * When no quality of service class and relative priority is specified for a
 * dispatch queue at the time of creation, a dispatch queue's quality of service
 * class is inherited from its target queue. The dispatch_get_global_queue()
 * function may be used to obtain a target queue of a specific quality of
 * service class, however the use of dispatch_queue_attr_make_with_qos_class()
 * is recommended instead.
 *
 * Blocks submitted to a serial queue whose target queue is another serial
 * queue will not be invoked concurrently with blocks submitted to the target
 * queue or to any other queue with that same target queue.
 *
 * The result of introducing a cycle into the hierarchy of target queues is
 * undefined.
 *
 * A dispatch source's target queue specifies where its event handler and
 * cancellation handler blocks will be submitted.
 *
 * A dispatch I/O channel's target queue specifies where where its I/O
 * operations are executed. If the channel's target queue's priority is set to
 * DISPATCH_QUEUE_PRIORITY_BACKGROUND, then the I/O operations performed by
 * dispatch_io_read() or dispatch_io_write() on that queue will be
 * throttled when there is I/O contention.
 *
 * For all other dispatch object types, the only function of the target queue
 * is to determine where an object's finalizer function is invoked.
 *
 * In general, changing the target queue of an object is an asynchronous
 * operation that doesn't take effect immediately, and doesn't affect blocks
 * already associated with the specified object.
 *
 * However, if an object is inactive at the time dispatch_set_target_queue() is
 * called, then the target queue change takes effect immediately, and will
 * affect blocks already associated with the specified object. After an
 * initially inactive object has been activated, calling
 * dispatch_set_target_queue() results in an assertion and the process being
 * terminated.
 *
 * If a dispatch queue is active and targeted by other dispatch objects,
 * changing its target queue results in undefined behavior.
 *
 *
 * @param object
 * The object to modify.
 * The result of passing NULL in this parameter is undefined.
 *
 * @param queue
 * The new target queue for the object. The queue is retained, and the  
 * previous target queue, if any, is released. 
 * If queue is DISPATCH_TARGET_QUEUE_DEFAULT, set the object's target queue
 * to the default target queue for the given object type.
void
dispatch_set_target_queue(dispatch_object_t object,
        dispatch_queue_t _Nullable queue);

应用1:改变queue的优先级与目标queue相同

dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
 
// 第一个参数为要设置优先级的queue,第二个参数queue是参照物,
// 既将第一个queue的优先级和第二个queue的优先级设置一样。
dispatch_set_target_queue(serialQueue, globalQueue);

应用2:
一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。

那该如何是好呢?就可以使用dispatch_set_target_queue了。

如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么多个串行queue在目标queue上就是串行执行的,不再是并行执行。

dispatch_get_current_queue可能导致死锁

dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
    
dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);

dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);


dispatch_async(queue1, ^{
    NSLog(@"1 in");
    [NSThread sleepForTimeInterval:3.f];
    NSLog(@"1 out");
});

dispatch_async(queue2, ^{
    NSLog(@"2 in");
    [NSThread sleepForTimeInterval:2.f];
    NSLog(@"2 out");
});
dispatch_async(queue3, ^{
    NSLog(@"3 in");
    [NSThread sleepForTimeInterval:1.f];
    NSLog(@"3 out");
});

// 1 in 1 out  , 2 in 2 out , 3in 3 out

4、dispatch_suspenddispatch_resume

简单理解,就是可以暂停、恢复队列上的任务。
需要注意的是,针对的是那些已经放入队列中,但是还没有被执行的任务,已经正在执行的会继续执行。


GCD注意事项:

造成死锁的情况:

  • 1、「异步执行+串行队列」嵌套「同步执行+串行队列」
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
    // task 1
    dispatch_sync(serialQueue, ^{
        // task 2
    });    
});  
  • 2、「同步执行+串行队列」嵌套「同步执行+串行队列」
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
    // task 1
    dispatch_sync(serialQueue, ^{
        // task 2
    });    
});  
  • 3、主线程中,「同步执行」到 「主线程队列」,很容易卡主主线程。
dispatch_sync(dispatch_get_main_queue(), ^{
    // block
});    
  • dispatch_get_current_queue 容易导致死锁
    在iOS6之后是弃用的,苹果只推荐在打印中使用,原因是容易导致死锁。

安全的主线程判断:

+ (BOOL)isMainQueue {
    static const void* mainQueueKey = @"mainQueue";
    static void* mainQueueContext = @"mainQueue";
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueContext, nil);
    });
    return dispatch_get_specific(mainQueueKey) == mainQueueContext;
}

主线程中也不绝对安全的 UI 操作


iOS 多线程:『GCD』详尽总结
深入理解 GCD
Concurrent Programming: APIs and Challenges
深入理解GCD之dispatch_queue

相关文章

  • 笔记整理:GCD

    一、概述 GCD是用纯C编写,效率很高;其内部自动维护一个线程池,自动管理线程的生命周期;其会利用CPU的多核特性...

  • iOS GCD见解之系列一

    最近在回顾多线程,现将最近整理的关于GCD的笔记整理出来,然后加上个人的见解。发布如下,如有错误,望指出! 线程:...

  • GCD整理

    系统提供的dispatch方法: 为了方便地使用 GCD,苹果提供了一些方法方便我们将 block 放在主线程 或...

  • GCD整理

    这边就整理下GCD一些常用的方式,以及举例子并打印结果说明相应的。一直想去整理然后嘛,要么没时间要么懒...拖到现...

  • GCD整理

    基本的数据结构 GCD的类都是struct定义的。 包括所有的父类的数据成员,都平铺重复的写在一个个的struct...

  • iOS多线程之GCD详解

    原文链接:整理多线程:GCD详解,如有侵权立即删除 一、GCD简介 为什么要用GCD呢? 二、GCD任务和队列 2...

  • 【Objective-C】GCD介绍

    整理自raywenderlich。 1.GCD是嘛? GCD是Grand Central Dispatch的缩写,...

  • GCD整理(一)

    整理一篇关于GCD的文章,自己以后要复习的时候也方便。 GCD(Grand Center Dispatch)异步执...

  • GCD整理(二)

    这篇会整理GCD常用的API 目录1、dispatch_after2、dispatch_apply3、dispat...

  • GCD 简单整理

    GCD (Grand Central Dispatch) 概念 关注两个概念:队列、任务。iOS 多线程方案:pt...

网友评论

      本文标题:笔记整理:GCD

      本文链接:https://www.haomeiwen.com/subject/ofpqeltx.html