返回

揭秘GCD函数与死锁根源,探索并发编程的奥秘

IOS

GCD 揭秘:同步与异步函数的机制

GCD 简介

Grand Central Dispatch (GCD) 是 Apple 提供的一个强大的并发框架,它允许开发者轻松地创建和管理并发任务。GCD 通过将任务分配给不同的队列来实现并发,从而使应用程序可以充分利用多核处理器的优势。

同步函数

同步函数在当前线程上阻塞,直到任务执行完毕。这意味着当前线程无法执行其他任务,直到同步任务完成。GCD 中常用的同步函数包括:

  • dispatch_sync() :在当前线程上同步执行一个任务。
  • dispatch_barrier_sync() :在当前线程上同步执行一个任务,并且在此期间阻止其他线程访问同一队列。

异步函数

异步函数在当前线程上不会阻塞。任务执行完毕后,GCD 会通过回调函数通知调用者。这意味着当前线程可以在任务执行期间继续执行其他任务。GCD 中常用的异步函数包括:

  • dispatch_async() :在后台队列上异步执行一个任务。
  • dispatch_apply() :并行执行一个任务集。

死锁的根源

死锁是指两个或多个线程相互等待,导致程序无法继续执行。在 GCD 中,死锁通常是由于不当使用同步函数造成的。例如,如果一个线程在串行队列中使用 dispatch_sync() 调用另一个线程中的任务,那么这两个线程就会发生死锁,因为第一个线程等待第二个线程完成任务,而第二个线程又等待第一个线程释放锁。

源码分析

为了更好地理解 GCD 的运行机制,我们可以分析其源码。 GCD 的源码位于 XNU 内核中,是一个庞大而复杂的系统。下面,我们重点分析与同步函数和异步函数相关的部分:

同步函数

static void _dispatch_sync_f_slow(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_barrier_sync_f_slow(queue, func, ctxt);
}

dispatch_sync() 实际上调用了 dispatch_barrier_sync_f_slow()

static void _dispatch_barrier_sync_f_slow(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_barrier_sync(queue, ^ {
                func(ctxt);
        });
}

dispatch_barrier_sync_f_slow() 将任务包装成一个 block,然后调用 dispatch_barrier_sync()

static void _dispatch_barrier_sync(dispatch_queue_t queue)
{
        dispatch_sync(queue, ^{});
}

dispatch_barrier_sync() 实际上调用了 dispatch_sync()

static void _dispatch_sync(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_assert_queue(queue);
        _dispatch_thread_override_mach_thread_np(YES);
        _dispatch_thread_override_mach_port_np(YES);
        _dispatch_root_queue_push(queue, func, ctxt, NULL);
        _dispatch_mach_thread_activity_v2();
        _dispatch_thread_override_mach_thread_np(NO);
        _dispatch_thread_override_mach_port_np(NO);
}

dispatch_sync() 首先检查当前线程是否是队列的线程,如果不是,则将其标记为队列的线程。然后,将任务包装成一个 block 并将其推入队列。接下来,它调用 _dispatch_mach_thread_activity_v2() 将当前线程标记为活动线程。最后,将当前线程标记为不是队列的线程。

异步函数

static void _dispatch_async_f_slow(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_barrier_async_f_slow(queue, func, ctxt);
}

dispatch_async() 实际上调用了 dispatch_barrier_async_f_slow()

static void _dispatch_barrier_async_f_slow(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_barrier_async(queue, ^ {
                func(ctxt);
        });
}

dispatch_barrier_async_f_slow() 将任务包装成一个 block,然后调用 dispatch_barrier_async()

static void _dispatch_barrier_async(dispatch_queue_t queue)
{
        dispatch_async(queue, ^{});
}

dispatch_barrier_async() 实际上调用了 dispatch_async()

static void _dispatch_async(dispatch_queue_t queue, dispatch_function_t func, void *ctxt)
{
        dispatch_assert_queue(queue);
        _dispatch_root_queue_push(queue, func, ctxt, NULL);
}

dispatch_async() 首先检查当前线程是否是队列的线程,如果不是,则将任务包装成一个 block 并将其推入队列。

结论

通过分析 GCD 的源码,我们可以更深入地了解其运行机制,从而在实际开发中有效地使用 GCD。同时,我们应该避免不当使用同步函数,以防止死锁的发生。

常见问题解答

  • 什么是 GCD?
    GCD 是一个并发框架,用于轻松创建和管理并发任务。
  • 同步函数和异步函数有什么区别?
    同步函数在当前线程上阻塞,直到任务完成;异步函数不会阻塞,任务完成后通过回调函数通知调用者。
  • 死锁是如何发生的?
    死锁是由不当使用同步函数造成的,例如在串行队列中使用 dispatch_sync() 调用另一个线程中的任务。
  • 如何避免死锁?
    尽量使用异步函数,并且谨慎使用同步函数。
  • GCD 中常用的同步函数和异步函数有哪些?
    同步函数:dispatch_sync()dispatch_barrier_sync(); 异步函数:dispatch_async()dispatch_apply()