底层探索-多线程之GCD(单例,栅栏函数、信号量、调度组、事件源)

385 阅读12分钟

本篇章从源码的角度来探索单例,栅栏函数、信号量、调度组、事件源

一:单例 dispatch_once

单例我们在开发中也是使用非常频繁,其中我们使用到了GCDdispatch_once函数

static dispatch_once_t token;
dispatch_once(&token, ^{
       // code
   });

定义如下:

#define dispatch_once _dispatch_once

libdispatch中找到_dispatch_once

image.png

这里对不同情况进行处理,dispatch_compiler_barrier()是对栅栏的处理,直接找到dispatch_once

image.png

最后调用dispatch_once_f

image.png

  • 将传入val包装成l,通过os_atomic_load从底层取出,关联到变量v上。如果v这个值等于DLOCK_ONCE_DONE,也就是已经处理过一次了,就会直接return返回。

_dispatch_once_gate_tryenter

image.png

_dispatch_once_gate_tryenter中进行原子操作,就是锁的处理,所有它是线程安全的。如果之前没有执行过,原子处理会比较它状态,进行解锁,最终会返回一个bool值,多线程情况下,只有一个能够获取锁返回yes

如果最终返回yes,则会调用_dispatch_once_callout,执行单例任务并对外广播。

image.png

看下_dispatch_once_gate_broadcast

image.png

token通过原子比对,如果不是done,则设为done。同时对_dispatch_once_gate_tryenter方法中的锁进行处理。

_dispatch_once_mark_done image.png

当标记为done之后,下次进来就直接返回了。

image.png

看上图中其中最还有一个_dispatch_once_wait,这是对多线程的处理,如果存在多线程,且没有获取到锁,就会调用_dispatch_once_wait,进行等待,这里开启了自旋锁,内部进行原子处理,在loop过程中,如果发现已经被其他线程设置once_done了,则会进行放弃处理。

image.png

一张图来概括:

image.png

二:栅栏 dispatch_barrier_async

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async 方法在两个操作组间形成栅栏。

2.1 基本使用

dispatch_barrier_async

- (void)demo2{
    dispatch_queue_t concurrentQueue = dispatch_queue_create("cc", DISPATCH_QUEUE_CONCURRENT);
    /* 1.异步函数 */
    dispatch_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"123");
    });
    
    dispatch_async(concurrentQueue, ^{
        sleep(2);
        NSLog(@"456");
    });

    /* 2. 栅栏函数 */ // - dispatch_barrier_sync

    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
        NSLog(@"789");
    });

    /* 3. 异步函数 */
    dispatch_async(concurrentQueue, ^{
        NSLog(@"加载那么多,喘口气!!!");
    });
    NSLog(@"**********起来干!!");
}

打印结果如下:

image.png 我们发现dispatch_barrier_async并不会阻塞主线程,所以结尾最先打印。但是会阻塞任务线程concurrentQueue,所以123456优先于789打印,最后打印加载那么多,喘口气

dispatch_barrier_sync 还是上述示例代码,将dispatch_barrier_async换成dispatch_barrier_sync之后,我来看下打印结果。

image.png

唯一的不同在于结尾变成了最后打印,也就是说dispatch_barrier_sync会阻塞当前线程也就是主线程,这点在使用过程中需要注意。

  • 注意事项

    • 栅栏函数和其他的任务必须在同一个队列中。
    • 只能使用自定义的并发队列,不能使用全局并发队列。

2.2 原理分析

dispatch_barrier_sync

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
    uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

跟踪源码来到_dispatch_barrier_sync_f -> _dispatch_barrier_sync_f_inline

image.png

其中_dispatch_queue_try_acquire_barrier_sync判断挂起,最终来到_dispatch_queue_try_acquire_barrier_sync_and_suspend,会对当前的状态state加一层处理,暂时放弃。

image.png 最终返回的是下层的OS控制处理。

另外,这里_dispatch_sync_f_slow还涉及到了死锁的处理,。

示例代码主线程同步造成死锁,查看调用堆栈涉及 _dispatch_sync_f_slow__DISPATCH_WAIT_FOR_QUEUE__ image.png

_dispatch_sync_f_slow

image.png

在该方法中,会将任务添加到队列,以主线程添加同步任务为例,会将同步任务添加到主队列。

__DISPATCH_WAIT_FOR_QUEUE__

image.png

可以理解为A任务要执行,可是之前又被系统安排为等待,那到底是执行还是等待呢?就产生了矛盾,相互等待,造成死锁。

我们回到_dispatch_barrier_sync_f_inline往下看。

根据堆栈信息,发现会调用_dispatch_sync_invoke_and_complete_recurse

image.png

训着这条轨迹,跟随这条调用路线 _dispatch_sync_invoke_and_complete_recurse -> _dispatch_sync_complete_recurse

image.png

这里是一个 do while循环,判断当前队列里面是否有barrier,有的话就dx_wakeup唤醒执行,直到任务执行完成了,才会执行_dispatch_lane_non_barrier_complete,表示当前队列任务已经执行完成了,并且没有栅栏函数了就会继续往下面的流程走。

#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

这里我们直接搜索dq_wakeup

image.png

根据不同的队列,走不同的方法,全局并发的是_dispatch_root_queue_wakeup,串行和并发的是_dispatch_lane_wakeup

首先看下自定义并发队列的_dispatch_lane_wakeup

image.png

  • 判断是否为barrier形式的,会调用_dispatch_lane_barrier_complete方法处理
  • 如果没有barrier形式的,则走正常的并发队列流程,调用_dispatch_queue_wakeup方法。

_dispatch_lane_barrier_complete

image.png

  • 如果是串行队列,则会进行等待,等待其他的任务执行完成,再按顺序执行

  • 如果是并发队列,则会调用_dispatch_lane_drain_non_barriers方法将栅栏之前的任务执行完成。

  • 最后会调用_dispatch_lane_class_barrier_complete方法,也就是把栅栏拔掉了,不拦了,从而执行栅栏之后的任务。

再来看全局并发队列的 _dispatch_root_queue_wakeup

image.png

  • 全局并发队列这个里面,并没有对barrier的判断和处理,就是按照正常的并发队列来处理。

  • 全局并发队列为什么没有对栅栏函数进行处理呢?因为全局并发队列除了被我们使用,系统也在使用。

  • 如果添加了栅栏函数,会导致队列运行的阻塞,从而影响系统级的运行,所以栅栏函数也就不适用于全局并发队列

_dispatch_barrier_sync_f_inline的最后,会来到_dispatch_lane_barrier_sync_invoke_and_complete对已经执行完成的任务进行下次层状态的释放。

image.png

三:信号量 dispatch_semaphore

GCD的使用过程中是无法控制并发数量的,但是我们可以"曲线救国",使用信号量来解决这个问题。

信号量Dispatch Semaphore,是一种持有计数的信号的东西。有如下三个方法。

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

3.1 基本使用

- (void)test {
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

    //任务1
    dispatch_async(queue, ^{

        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        NSLog(@"执行任务1");

        sleep(1);

        NSLog(@"任务1完成");

        dispatch_semaphore_signal(sem);

    });
    
    //任务2
    dispatch_async(queue, ^{

        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        NSLog(@"执行任务2");

        sleep(1);

        NSLog(@"任务2完成");

        dispatch_semaphore_signal(sem);

    });
    
    //任务3
    dispatch_async(queue, ^{

        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        NSLog(@"执行任务3");

        sleep(1);

        NSLog(@"任务3完成");

        dispatch_semaphore_signal(sem);

    });

}

打印结果:

image.png

  • 我们将初始的信号量设置为1,也就是控制了最大并发数就是1

3.2 原理分析

3.2.1 dispatch_semaphore_create

dispatch_semaphore_create主要是初始化信号量,并设置GCD的最大并发数,其最大并发数必须大于等于0

image.png

3.2.2 dispatch_semaphore_signal

long
dispatch_semaphore_signal(dispatch_semaphore_t dsema){
    long value = os_atomic_inc2o(dsema, dsema_value, release);
    if (likely(value > 0)) {
        return 0;
    }

    if (unlikely(value == LONG_MIN)) {
        DISPATCH_CLIENT_CRASH(value,
        "Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);
}

os_atomic_inc2o是原子操作自增加1,然后会判断,如果value > 0,就会返回0。但是如果加一次后依然小于0,则会报异常:Unbalanced call to dispatch_semaphore_signal(),然后会调用_dispatch_semaphore_signal_slow方法的,做下层的处理,进入长等待。

3.2.3 dispatch_semaphore_wait

long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
    long value = os_atomic_dec2o(dsema, dsema_value, acquire);
    if (likely(value >= 0)) {
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}

os_atomic_dec2o是原子操作自减1,然后会判断value >= 0,返回成功。如果结果小于0,则会调用_dispatch_semaphore_wait_slow进行长等待。

_dispatch_semaphore_wait_slow 会根据传入的超时时间timeout,进行分开处理。

image.png

整体图:

image.png

四:调度组 dispatch_group

4.1 基本使用

用法一

- (void)demoTest {

    // 调度组
    dispatch_group_t group = dispatch_group_create();
    
    // 队列
    dispatch_queue_t queue = dispatch_queue_create("cc", DISPATCH_QUEUE_CONCURRENT);
    
    // 将任务添加到队列和调度组
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"下载 A %@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"下载 B %@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(1);
        NSLog(@"下载 C %@",[NSThread currentThread]);
    });
    
    // 异步 : 调度组中的所有异步任务执行结束之后,在这里得到统一的通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"下载完成 %@",[NSThread currentThread]);
    });
    // 同步 : 一直等到调度组中所有的任务都执行结束以后才执行后面的代码
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

    // 验证调度组是否是异步
    NSLog(@"end");
}

image.png

  • 当A,B,C 任务完成之后才会打印下载完成

用法二
使用dispatch_group_enterdispatch_group_leave

/**
 * 队列组 dispatch_group_enter、dispatch_group_leave
 */
- (void)groupEnterAndLeave {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务 2
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
    
        NSLog(@"group---end");
    });
}

dispatch_group_enterdispatch_group_leave 组合,其实等同于dispatch_group_async,但是两者必须成对出现。

4.2 源码分析

4.2.1 dispatch_group_create

dispatch_group_create

dispatch_group_t
dispatch_group_create(void){
    return _dispatch_group_create_with_count(0);
}

会调用_dispatch_group_create_with_count,并传入固定默认值0,内部进行组的定义和赋值操作。

static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n){
    // 🌹 定义
    dispatch_group_t dg =     _dispatch_object_alloc(DISPATCH_VTABLE(group),
    sizeof(struct dispatch_group_s));
    // 🌹 赋值操作
    dg->do_next = DISPATCH_OBJECT_LISTLESS;

    dg->do_targetq = _dispatch_get_default_queue(false);

    if (n) {
        os_atomic_store2o(dg, dg_bits,
        (uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
        os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
    }
    return dg;
}

4.2.2 dispatch_group_enter

void
dispatch_group_enter(dispatch_group_t dg)
{
    // The value is decremented on a 32bits wide atomic so that the carry
    // 🌹 for the 0 -> -1 transition is not propagated to the upper 32bits.

    uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
    uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
    if (unlikely(old_value == 0)) {
        _dispatch_retain(dg); // <rdar://problem/22318411>
    }

    if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
    DISPATCH_CLIENT_CRASH(old_bits,
    "Too many nested calls to dispatch_group_enter()");
    }
}

os_atomic_sub_orig2o会对dg->dg.bits进行减的操作,0->-1的变化。

4.2.3 dispatch_group_leave

void
dispatch_group_leave(dispatch_group_t dg)
{
    // The value is incremented on a 64bits wide atomic so that the carry for
    // 🌹 the -1 -> 0 transition increments the generation atomically.
    uint64_t new_state, old_state = os_atomic_add_orig2o(dg, dg_state,//原子递增 ++
            DISPATCH_GROUP_VALUE_INTERVAL, release);
    uint32_t old_value = (uint32_t)(old_state & DISPATCH_GROUP_VALUE_MASK);
    //🌹 根据状态,唤醒
    if (unlikely(old_value == DISPATCH_GROUP_VALUE_1)) {
        old_state += DISPATCH_GROUP_VALUE_INTERVAL;
        do {
            new_state = old_state;
            if ((old_state & DISPATCH_GROUP_VALUE_MASK) == 0) {
                new_state &= ~DISPATCH_GROUP_HAS_WAITERS;
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            } else {
                // If the group was entered again since the atomic_add above,
                // we can't clear the waiters bit anymore as we don't know for
                // which generation the waiters are for
                new_state &= ~DISPATCH_GROUP_HAS_NOTIFS;
            }
            if (old_state == new_state) break;
        } while (unlikely(!os_atomic_cmpxchgv2o(dg, dg_state,
                old_state, new_state, &old_state, relaxed)));
        return _dispatch_group_wake(dg, old_state, true);//唤醒
    }
    //-1 -> 0, 0+1 -> 1,即多次leave,会报crash,简单来说就是enter-leave不平衡
    if (unlikely(old_value == 0)) {
        DISPATCH_CLIENT_CRASH((uintptr_t)old_value,
                "Unbalanced call to dispatch_group_leave()");
    }
}

os_atomic_add_orig2o会进行自增的操作,old_state就为0,oldvalue也是0,根据查看DISPATCH_GROUP_VALUE_1old_value == DISPATCH_GROUP_VALUE_1)条件是不成立的,最终会来到_dispatch_group_wake进行唤醒,唤醒的就是dispatch_group_notify方法。

换句话说,如果不调用dispatch_group_leave方法,也就不会唤醒dispatch_group_notify,下面的流程也就不会执行了。大家可以试试只进组不出组的情况,dispatch_group_notify肯定不会被调用。

4.2.4 dispatch_group_notify

DISPATCH_ALWAYS_INLINE
static inline void
_dispatch_group_notify(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dsn)
{
    uint64_t old_state, new_state;
    dispatch_continuation_t prev;

    dsn->dc_data = dq;
    _dispatch_retain(dq);
    //🌹 获取dg底层的状态标识码,通过os_atomic_store2o获取的值,即从dg的状态码 转成了 os底层的state
    prev = os_mpsc_push_update_tail(os_mpsc(dg, dg_notify), dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) _dispatch_retain(dg);
    os_mpsc_push_update_prev(os_mpsc(dg, dg_notify), prev, dsn, do_next);
    if (os_mpsc_push_was_empty(prev)) {
        os_atomic_rmw_loop2o(dg, dg_state, old_state, new_state, release, {
            new_state = old_state | DISPATCH_GROUP_HAS_NOTIFS;
            if ((uint32_t)old_state == 0) { //如果等于0,则可以进行释放了
                os_atomic_rmw_loop_give_up({
                    return _dispatch_group_wake(dg, new_state, false);//唤醒
                });
            }
        });
    }
}

old_state等于0的情况下,才会去唤醒相关的同步或者异步函数的执行,也就是 block里面的执行。

  • 在上面dispatch_group_leave分析中,我们已经得到old_state结果等于0

  • 所以这里也就解释了dispatch_group_enterdispatch_group_leave为什么要配合起来使用的原因,通过这种控制,有加有减,避免异步的影响,能够及时唤醒并调用dispatch_group_notify方法

  • 我们注意到在dispatch_group_leave里面也有调用_dispatch_group_wake方法,这是因为异步的执行,任务是执行耗时的,有可能dispatch_group_leave这行代码还没有走,就先走了dispatch_group_notify方法,但这时候dispatch_group_notify方法里面的任务并不会执行,只是把任务添加到 group

  • 它会等dispatch_group_leave执行了被唤醒才执行,这样就保证了异步时,dispatch_group_notify里面的任务不丢弃,可以正常执行。

4.2.5 dispatch_group_async

上面提到过dispatch_group_enterdispatch_group_leave 组合,其实等同于dispatch_group_async,下面我们跟着源码看看是不是这样?

void
dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_block_t db)
{

    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_GROUP_ASYNC;
    dispatch_qos_t qos;
    //🌹任务包装器
    qos = _dispatch_continuation_init(dc, dq, db, 0, dc_flags);
    //🌹处理任务
    _dispatch_continuation_group_async(dg, dq, dc, qos);
}

_dispatch_continuation_group_async封装了进组的操作

static inline void
_dispatch_continuation_group_async(dispatch_group_t dg, dispatch_queue_t dq,
        dispatch_continuation_t dc, dispatch_qos_t qos)
{
    dispatch_group_enter(dg);//🌹进组
    dc->dc_data = dg;
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);//🌹异步操作
}

按照猜想有进就有出。

- (void)groupDemo{
    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_group_enter(group);

    dispatch_async(queue, ^{

        //创建调度组
        🌹 任务1
        NSLog(@"任务1");

        sleep(1);

        dispatch_group_leave(group);

    });
    dispatch_group_async(group, queue, ^{;
        🌹 任务2
        NSLog(@"任务2");

    });
    dispatch_group_notify(group, dispatch_get_main_queue(),^{
        NSLog(@"终于到我执行了");
    });
}

在任务2打上断点,查看堆栈,调用了_dispatch_client_callout

image.png

全局搜索_dispatch_client_callout的调用,在_dispatch_continuation_with_group_invoke进行了调用。

image.png

总结

  • enter-leave只要成对就可以,不管远近
  • dispatch_group_enter在底层是通过C++函数,对group的value进行--操作(即0 -> -1)
  • dispatch_group_leave在底层是通过C++函数,对group的value进行++操作(即-1 -> 0)
  • dispatch_group_notify在底层主要是判断group的state是否等于0,当等于0时,就通知
  • block任务的唤醒,可以通过dispatch_group_leave,也可以通过dispatch_group_notify
  • dispatch_group_async 等同于enter - leave,其底层的实现就是enter-leave

image.png

五:事件源 dispatch_source

Dispatch Source BSD系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生事件时在应用程序编程方执行处理的技术。

它的CPU负荷非常小,尽量不占用资源。当事件发生时,Dispatch Source 会在指定的Dispatch Queue中执行事件的处理。

  • dispatch_source_create :创建源
  • dispatch_source_set_event_handler: 设置源的回调
  • dispatch_source_merge_data: 源事件设置数据
  • dispatch_source_get_data: 获取源事件的数据
  • dispatch_resume:恢复继续
  • dispatch_suspend:挂起

我们在日常开发中,经常会使用计时器NSTimer,例如发送短信的倒计时,或者进度条的更新。但是NSTimer需要加入到NSRunloop中,还受到mode的影响。收到其他事件源的影响,被打断,当滑动scrollView的时候,模式切换,定时器就会停止,从而导致timer的计时不准确。

GCD提供了一个解决方案dispatch_source来出来类似的这种需求场景。

  • 时间较准确,CPU负荷小,占用资源少
  • 可以使用子线程,解决定时器跑在主线程上卡UI问题
  • 可以暂停,继续,不用像NSTimer一样需要重新创建

使用示例 - 定时器

- (void)use033{
    //倒计时时间
    __block int timeout = 3;

    //创建队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

    //创建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);

    //设置1s触发一次,0s的误差
    /*
     - source 分派源
     - start 数控制计时器第一次触发的时刻。参数类型是 dispatch_time_t,这是一个opaque类型,我们不能直接操作它。我们得需要 dispatch_time 和 dispatch_walltime 函数来创建它们。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
     - interval 间隔时间
     - leeway 计时器触发的精准程度
     */
    dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);

     //触发的事件
    dispatch_source_set_event_handler(timer, ^{
        //倒计时结束,关闭
        if (timeout <= 0) {
            //取消dispatch源
            dispatch_source_cancel(timer);
        }else{
            timeout--;

            dispatch_async(dispatch_get_main_queue(), ^{
                //更新主界面的操作
                NSLog(@"倒计时 - %d", timeout);
            });
        }
    });

    //开始执行dispatch源
    dispatch_resume(timer);
}
  • 使用定时器NSTimer需要加入到NSRunloop,导致计数不准确,可以使用Dispatch Source来解决

  • Dispatch Source的使用,要注意恢复挂起平衡

  • sourcesuspend状态下,如果直接设置 source = nil 或者重新创建 source 都会造成 crash。正确的方式是在resume状态下调用 dispatch_source_cancel(source)后再重新创建。

  • 因为 dispatch_source_set_event_handle回调是block,在添加到source的链表上时会执行copy并被source强引用,如果block里持有了selfself又持有了source的话,就会引起循环引用。所以正确的方法是使用weak+strong或者提前调dispatch_source_cancel取消timer

参考:

iOS底层探索之多线程(十二)—GCD源码分析(事件源dispatch_source)
iOS GCD 之 底层原理分析