iOS开发探索多线程GCD任务示例详解

目录
  • 引言
    • 同步任务
    • 死锁
    • 异步任务
  • 总结

引言

在上一篇文章中,我们探寻了队列是怎么创建的,串行队列和并发队列之间的区别,接下来我们在探寻一下GCD的另一个核心 - 任务

同步任务

void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

我们先通过lldb查看其堆栈信息,分别查看其正常运行和死锁状态的信息

我们再通过源码查询其实现

#define _dispatch_Block_invoke(bb) ((dispatch_function_t)((struct Block_layout *)bb)->invoke)
void dispatch_sync(dispatch_queue_t dq, dispatch_block_t work) {
    uintptr_t dc_flags = DC_FLAG_BLOCK;
    if (unlikely(_dispatch_block_has_private_data(work))) {
        return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    }
    _dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}

其通过_dispatch_Block_invoke将函数包装成了一个block,后继续向下传递,也就是说我们的代码是通过这个block来进行的执行,继续查询其的传递

static inline void _dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags) {
    if (likely(dq->dq_width == 1)) {
        return _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
    }
    if (unlikely(dx_metatype(dq) != _DISPATCH_LANE_TYPE)) {
        DISPATCH_CLIENT_CRASH(0, "Queue type doesn't support dispatch_sync");
    }
    dispatch_lane_t dl = upcast(dq)._dl;
    // Global concurrent queues and queues bound to non-dispatch threads
    // always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE
    if (unlikely(!_dispatch_queue_try_reserve_sync_width(dl))) {
        return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
    }
    if (unlikely(dq->do_targetq->do_targetq)) {
        return _dispatch_sync_recurse(dl, ctxt, func, dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);
    _dispatch_sync_invoke_and_complete(dl, ctxt, func DISPATCH_TRACE_ARG(
    _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags)));
}

我们发现_dispatch_sync_f_inline中存在我们查看的堆栈信息时的两个函数_dispatch_sync_f_slow、_dispatch_sync_invoke_and_complete

static void _dispatch_sync_f_slow(dispatch_queue_class_t top_dqu, void *ctxt, dispatch_function_t func, uintptr_t top_dc_flags, dispatch_queue_class_t dqu, uintptr_t dc_flags) {
    ...省略部分...
    struct dispatch_sync_context_s dsc = {
        ...省略部分...
        .dsc_func    = func,
    };
    __DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);
    ...省略部分...
    _dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags DISPATCH_TRACE_ARG(&dsc));
}
static void _dispatch_sync_invoke_and_complete_recurse(dispatch_queue_class_t dq, void *ctxt, dispatch_function_t func, uintptr_t dc_flags DISPATCH_TRACE_ARG(void *dc)) {
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_trace_item_complete(dc);
    _dispatch_sync_complete_recurse(dq._dq, NULL, dc_flags);
}
static void _dispatch_sync_invoke_and_complete(dispatch_lane_t dq, void *ctxt, dispatch_function_t func DISPATCH_TRACE_ARG(void *dc)) {
    _dispatch_sync_function_invoke_inline(dq, ctxt, func);
    _dispatch_trace_item_complete(dc);
    _dispatch_lane_non_barrier_complete(dq, 0);
}

我们发现,两种堆栈信息的函数的func这个block都是传递给的_dispatch_sync_function_invoke_inline

static inline void _dispatch_sync_function_invoke_inline(dispatch_queue_class_t dq, void *ctxt, dispatch_function_t func) {
    dispatch_thread_frame_s dtf;
    _dispatch_thread_frame_push(&dtf, dq);
    _dispatch_client_callout(ctxt, func);
    _dispatch_perfmon_workitem_inc();
    _dispatch_thread_frame_pop(&dtf);
}
void _dispatch_client_callout(void *ctxt, dispatch_function_t f) {
    _dispatch_get_tsd_base();
    void *u = _dispatch_get_unwind_tsd();
    if (likely(!u)) return f(ctxt);
    _dispatch_set_unwind_tsd(NULL);
    f(ctxt); // 调用block函数,执行任务
    _dispatch_free_unwind_tsd();
    _dispatch_set_unwind_tsd(u);
}

最终通过_dispatch_client_callout函数执行的我们的block代码,和我们正常执行的堆栈信息相符合。

通过我们的源码的探索,我们发现了在同步任务中并没有任何关于开辟线程的操作,而且任务并没有保存而是直接执行的。

死锁

我们在获取的堆栈信息发现了崩溃调用的函数是__DISPATCH_WAIT_FOR_QUEUE__,在源码中查看

static void __DISPATCH_WAIT_FOR_QUEUE__(dispatch_sync_context_t dsc, dispatch_queue_t dq) {
    uint64_t dq_state = _dispatch_wait_prepare(dq);
    if (unlikely(_dq_state_drain_locked_by(dq_state, dsc->dsc_waiter))) {
        DISPATCH_CLIENT_CRASH((uintptr_t)dq_state,
        "dispatch_sync called on queue"
        "already owned by current thread"); // 当前线程已存在这个同步队列
    }
    ...省略部分...
}
// crash条件
static inline bool _dq_state_drain_locked_by(uint64_t dq_state, dispatch_tid tid) {
    return _dispatch_lock_is_locked_by((dispatch_lock)dq_state, tid);
}
static inline bool _dispatch_lock_is_locked_by(dispatch_lock lock_value, dispatch_tid tid) {
    // equivalent to _dispatch_lock_owner(lock_value) == tid
    // lock_value 当前队列, tid 当前线程
    return ((lock_value ^ tid) & DLOCK_OWNER_MASK) == 0;
}

通过这里可以看到 崩溃的条件:串行队列,当前队列已在当前线程中。

异步任务

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

一样先通过lldb查看一下堆栈信息

很明显的和同步任务的区别是里面有pthread.dylib的调用,我们还是来通过源码看一下吧。

void dispatch_async(dispatch_queue_t dq, dispatch_block_t work) {
    dispatch_continuation_t dc = _dispatch_continuation_alloc();
    uintptr_t dc_flags = DC_FLAG_CONSUME;
    dispatch_qos_t qos;
    qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    _dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
}
static inline dispatch_qos_t _dispatch_continuation_init(dispatch_continuation_t dc, dispatch_queue_class_t dqu, dispatch_block_t work, dispatch_block_flags_t flags, uintptr_t dc_flags) {
    void *ctxt = _dispatch_Block_copy(work);
    ...省略部分...
    dispatch_function_t func = _dispatch_Block_invoke(work);
}
static inline dispatch_qos_t _dispatch_continuation_init_f(dispatch_continuation_t dc, dispatch_queue_class_t dqu, void *ctxt, dispatch_function_t f, dispatch_block_flags_t flags, uintptr_t dc_flags) {
    pthread_priority_t pp = 0;
    dc->dc_flags = dc_flags | DC_FLAG_ALLOCATED;
    dc->dc_func = f;
    dc->dc_ctxt = ctxt;
    ……
    _dispatch_continuation_voucher_set(dc, flags);
    return _dispatch_continuation_priority_set(dc, dqu, pp, flags);
}

异步任务将任务先用_dispatch_continuation_init进行了copy操作,保存了任务,同时和同步函数一样将任务用_dispatch_Block_invoke进行了封装,然后将copy的任务和封装的block赋值给dispatch_continuation_t dc,也就是相当于保存了队列中添加的任务,最终返回一个dispatch_qos_t的对象qos。

#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
static inline void _dispatch_continuation_async(dispatch_queue_class_t dqu, dispatch_continuation_t dc, dispatch_qos_t qos, uintptr_t dc_flags) {
    return dx_push(dqu._dq, dc, qos);
}

源码中全局搜索dq_push,我们在熟悉的文件Dispatch Source/init.c中找到了每种队列对应的dq_push

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_serial, lane,
    .do_type        = DISPATCH_QUEUE_SERIAL_TYPE,
    ......
    .dq_push        = _dispatch_lane_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
    .do_type        = DISPATCH_QUEUE_CONCURRENT_TYPE,
    ......
    .dq_push        = _dispatch_lane_concurrent_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
    .do_type        = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
    ......
    .dq_push        = _dispatch_root_queue_push,
);
DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_main, lane,
    .do_type        = DISPATCH_QUEUE_MAIN_TYPE,
    ......
    .dq_push        = _dispatch_main_queue_push,
);

我们通过最常用的global的_dispatch_root_queue_push来进行探索

void _dispatch_root_queue_push(dispatch_queue_global_t rq, dispatch_object_t dou, dispatch_qos_t qos) {
...省略部分...
    #if HAVE_PTHREAD_WORKQUEUE_QOS
        if (_dispatch_root_queue_push_needs_override(rq, qos)) {
            return _dispatch_root_queue_push_override(rq, dou, qos);
        }
    #else
        (void)qos;
    #endif
    _dispatch_root_queue_push_inline(rq, dou, dou, 1);
}
static void _dispatch_root_queue_push_override(dispatch_queue_global_t orig_rq, dispatch_object_t dou, dispatch_qos_t qos) {
    ......
    _dispatch_root_queue_push_inline(rq, dc, dc, 1);
}

我们可以看到其内部是调用的_dispatch_root_queue_push_inline函数,进一步说调用_dispatch_root_queue_poke_slow

static void _dispatch_root_queue_poke_slow(dispatch_queue_global_t dq, int n, int floor) {
    ......
    _dispatch_root_queues_init();
    ...利用线程池调度任务等相关代码...
}
static inline void _dispatch_root_queues_init(void) {
    dispatch_once_f(&_dispatch_root_queues_pred, NULL,
    _dispatch_root_queues_init_once);
}

在_dispatch_root_queues_init_once中进行了线程对任务的调度

_dispatch_worker_thread2, _dispatch_worker_thread2 ->

_dispatch_root_queue_drain -> _dispatch_continuation_pop_inline ->

_dispatch_continuation_invoke_inline,_dispatch_root_queue_poke_slow

进行了线程池的相关操作

也就是我们在堆栈信息中pthread.dylib的调用的原因。这些异步调度我们已经无法进行下一步查看了,所以还是看回我们的堆栈信息,很明显函数的执行仍是通过_dispatch_client_callout进行的执行。

总结

  • 同步任务:任务立即执行,无线程相关操作,会阻塞当前线程
  • 异步任务:保存任务,进行线程相关操作,可以开辟子线程,不会阻塞当前线程
  • 死锁:在当前线程同步(和当前队列相关)同步的向里面添加任务,就会死锁

以上就是iOS开发探索多线程GCD任务示例详解的详细内容,更多关于iOS开发多线程GCD任务的资料请关注我们其它相关文章!

(0)

相关推荐

  • IOS开发之多线程NSThiread GCD NSOperation Runloop

    IOS中的进程和线程 通长来说一个app就是一个进程 ios开发中较少的运用进程间的通信(XPC),绝大多数使用线程. 在ios开发中,为了保证流畅性以及线程安全,所有与UI相关的操作都应该放在主线程,所以有时候主线程也叫UI线程. 影响UI体验,耗时时间较长的操作,尽量放到非主线程中.比如网络请求以及和本地的IO操作. 在IOS开发中有关于多线程的知识点主要包括:NSThread.GCD.NSOperation和Runloop NSThread NSthread就是一个线程,它的底层是对pth

  • iOS 多线程总结之GCD的使用详解

    进程与线程 进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行. 线程是指进程内的一个执行单元,也是进程内的可调度实体. 与进程的区别: (1)地址空间:线程是进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间; (2)资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源 (3)线程是处理器调度的基本单位,但进程不是. (4)二者均可并发执行. GCD 1.什么是GCD? 全

  • iOS开发探索多线程GCD队列示例详解

    目录 引言 进程与线程 1.进程的定义 2.线程的定义 3. 进程和线程的关系 4. 多线程 5. 时间片 6. 线程池 GCD 1.任务 2.队列 3.死锁 总结 引言 在iOS开发过程中,绕不开网络请求.下载图片之类的耗时操作,这些操作放在主线程中处理会造成卡顿现象,所以我们都是放在子线程进行处理,处理完成后再返回到主线程进行展示. 多线程贯穿了我们整个的开发过程,iOS的多线程操作有NSThread.GCD.NSOperation,其中我们最常用的就是GCD. 进程与线程 在了解GCD之前

  • iOS开发探索多线程GCD常用函数

    目录 正文 单例 栅栏函数 调度组 dispatch_group_t 信号量 dispatch_semaphore_t dispatch_source 总结 正文 前篇文章我们了解了GCD的任务的原理,接下来我们在探索一下GCD中我们开发常用的函数 单例 下面我们从源码中看一下我们创建单例的时候使用的dispatch_once,都做了什么,是通过什么操作保证全局唯一的 void dispatch_once(dispatch_once_t *val, dispatch_block_t block)

  • ios开发中的容错处理示例详解

    前言 后台服务器返回给客户端的值有时会是null,有时会是"<null>",直接赋值并进行后续操作有时会导致崩溃. 之前的处理方式都是尽量让后台服务器返回数据时不返回null或者是"<null>",但是他们还是时不时返回这些数据,所以app时不时就会出现闪退现象.一出现这种问题,调试后发现是他们返回null或者是"null"的数据类型,因为是线上的问题,所以让他们直接在后台将出现问题的字段进行处理就好了.久而久之,发现这种

  • IOS开发基础之二维数组详解

    IOS开发基础之二维数组详解 首先我们知道OC中是没有二维数组的,二维数组是通过一位数组的嵌套实现的,但是别忘了我们有字面量,实际上可以和C/C++类似的简洁地创建和使用二维数组.这里总结了创建二维数组的两种方法以及数组的访问方式. 通过字面量创建和使用二维数组(推荐) // 1.字面量创建二维数组并访问(推荐) NSArray *array2d = @[ @[@11,@12,@13], @[@21,@22,@23], @[@31,@32,@33] ]; // 字面量访问方式(推荐) NSLog

  • IOS 开发中画扇形图实例详解

    IOS 开发中画扇形图实例详解 昨天在做项目中,遇到一个需要显示扇形图的功能,网上搜了一下,发现code4app里面也没有找到我想要的那种类似的效果,没办法了,只能自己学习一下如何画了. 首先我们需要了解一个uiview的方法 -(void)drawRect:(CGRect)rect 我们知道了这个方法,就可以在自定义UIView的子类的- (void)drawRect:(CGRect)rect里面绘图了,关于drawrect的调用周期,网上也是一找一大堆,等下我会整理一下,转载一篇供你们参考.

  • iOS开发系列--地图与定位源代码详解

    概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式.例如你到了一个陌生的地方想要查找附近的酒店.超市等就可以打开软件搜索周边;类似的,还有很多团购软件可以根据你所在的位置自动为你推荐某些商品.总之,目前地图和定位功能已经大量引入到应用开发中.今天就和大家一起看一下iOS如何进行地图和定位开发. 定位 地图 定位 要实现地图.导航功能,往往需要先熟悉定位功能,在iO

  • iOS开发系列--通知与消息机制详解

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地通知:另一类是推送通知,也叫远程通知.两种通知在iOS中的表现一致,可以通过横幅或者弹出提醒两种形式告诉用户,并且点击通知可以会打开应用程序,但是实现原理却完全不同.今天就和大家一块去看一下如何在iOS中实现这两种机制,并且在文章后面会补充通知中心的内容避免初学者对两种概念的混淆. 本地通知 本地通

  • Android多线程断点续传下载示例详解

    一.概述 在上一篇博文<Android多线程下载示例>中,我们讲解了如何实现Android的多线程下载功能,通过将整个文件分成多个数据块,开启多个线程,让每个线程分别下载一个相应的数据块来实现多线程下载的功能.多线程下载中,可以将下载这个耗时的操作放在子线程中执行,即不阻塞主线程,又符合Android开发的设计规范. 但是当下载的过程当中突然出现手机卡死,或者网络中断,手机电量不足关机的现象,这时,当手机可以正常使用后,如果重新下载文件,似乎不太符合大多数用户的心理期望,那如何实现当手机可以正

  • iOS中捕获日志与异常示例详解

    前言 在平时自己调试的时候,可以直接连接电脑,直接在窗口中查看结果.但是在测试人员测试,或者灰度测试的时候,怎么才能拿到日志呢?最先想到的肯定是输出到本地文件,然后在需要的时候进行上传. 分享一段之前找到的方法,下面的代码提供了两个主要功能: – 把日志输出到文件中 – 捕捉异常信息 [解析都写在注释中了] 示例代码 - (void)redirectNSLogToDocumentFolder { //如果已经连接Xcode调试则不输出到文件 //该函数用于检测输出 (STDOUT_FILENO)

  • IOS 开发之应用唤起实现原理详解

    一.什么是iOS应用唤起 IOS中的应用唤起用来实现以下功能:在浏览器中可以通过某些方式打开IOS手机本地的app,如果该app没有安装可以跳转到该应用对应的App Store的下载页. 二.App store下载页连接 App store中某个应用的下载页连接形如:https://itunes.apple.com/us/app/id399608199.在PC端浏览器打开该连接会跳转到应用详情页的PC端界面.在Safari中打开该连接,浏览器会询问是否在App Store中打开该连接,选择打开即

  • iOS富文本的使用方法示例详解

    前言 常常会有一段文字显示不同的颜色和字体,或者给某几个文字加删除线或下划线的需求. 使用富文本NSMuttableAttstring(带属性的字符串),上面的一些需求都可以很简便的实现. 最近想实现一个功能,如图: 每月价格 最初实现的时候想到了用两个Label,来实现,第一个显示¥4000,设置一个字体,第二个显示/月,设置另一个字体.这样就能实现这个效果了,但是最后想一想还是用富文本比较好,顺便可以学习一下. 今天我们先实现这个简单的效果. 先创建一个Label: -(UILabel *)

  • Servlet开发JavaWeb工程示例详解

    一.什么是Servlet? Servlet是在服务器上运行的小程序,也就是一个Java类,但比较特殊,不需要new,自动就可以运行.也有创建.垃圾回收和销毁过程.Servlet是JavaWeb的三大组件之一(Servlet.Filter.Listener),它属于动态资源.Servlet的作用是处理请求,服务器会把接收到的请求交给Servlet来处理,在Servlet中通常需要: 接收请求数据: 处理请求: 完成响应. 例如客户端发出登录请求,或者输出注册请求,这些请求都应该由Servlet来完

随机推荐

其他