美文网首页iOS-多线程
iOS Objective-C GCD之queue(队列)篇

iOS Objective-C GCD之queue(队列)篇

作者: just东东 | 来源:发表于2020-10-16 16:03 被阅读0次

    iOS Objective-C GCD之queue(队列)篇

    GCD全称Grand Central Dispatch,是苹果为多核的并行运算提出的解决方案,由C语言实现,GCD会自动利用更多的CPU内核,自动管理线程的声明周期。GCD的底层实现来自libdispatch库,我们可以在Apple Open Source下载各个版本的libdispatch源码。在这篇文章中我们着重介绍GCD中的队列。

    1. 队列

    队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

    在代码编程中,就是我们在队尾插入一块任务,然后Runloop调度线程去执行任务,执行完就出队,继续执行下一个。这些任务可以是一个简单的运算,也可以是加载一张图片,在iOS中也可以是一个block代码块。

    在使用GCD的时候,我们都会获取一个队列,所以说GCD的使用离不开队列,实际上获取的队列的类型是dispatch_queue_t

    队列分为串行并行(并发)

    • 串行队列中任务只会顺序执行,类似于公交车,前门一个一个的上车,后门一个一个的下车
    • 并行队列中任务可以并行执行,类似于火车或地铁,可以多个门口上车,多个门口下车
    队列.jpg

    在系统中队列还分为全局队列主队列

    • 全局队列 由系统创建,在iOS中苹果给程序员提供了一个global_queue的全局并发队列,,在多线程开发中如果没有特殊需求,在执行异步任务的时候可以直接使用全局队列。
    • 主队列 同样由系统创建,每个应用程序对应唯一一个主队列,主队列与主线程是绑定的,在iOS中主队列通常都是用来更新UI的。

    1.1 iOS中创建队列的方式

    1.1.1 获取主队列

    • 获取主队列使用的函数dispatch_get_main_queue()
    • 主队列是在应用启动的时,main函数执行前系统自动创建的,这个队列被绑定在主线程上。

    1.1.2 获取全局队列

    • 获取全局队列的函数dispatch_get_global_queue(<#intptr_t identifier#>, <#uintptr_t flags#>)
    • 第一个参数我们一般传0,但是系统也有一些枚举值如下:
    枚举 优先级
    DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
    DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
    DISPATCH_QUEUE_PRIORITY_LOW -2 低优先级
    DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
    • 第二个参数是标签flag,我们在使用的时候传0的时候也很多

    1.1.3 创建一个新队列

    • 创建新队列使用的函数:dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
    • 第一个参数label用来标识queue的字符串一般使用倒域名的形式"com.xxx.xxx"
    • 第二个参数attr队列属性,DISPATCH_QUEUE_SERIAL也就是NULL会创建串行队列,DISPATCH_QUEUE_CONCURRENT会创建并发队列。

    1.2 函数

    将任务添加到队列,并且指定执行任务的函数

    • 任务使用block封装,没有参数也没有返回值
    • 执行任务的异步函数:dispatch_async
      • 不用等待当前语句执行完毕就可以执行下一条语句
      • 会开启线程执行block的任务
      • 异步是多线程的代名词
    • 执行任务的同步函数dispatch_sync
      • 必须等待当前语句执行完毕才会执行下一条语句
      • 不会开启线程
      • 在当前线程执行block任务

    1.3 队列和函数

    同步函数和串行队列:

    • 不会开启线程在当前线程执行任务
    • 任务串行执行,任务一个接着一个
    • 会产生阻塞

    同步函数并发队列:

    • 不会开启线程,在当前线程执行任务
    • 任务一个接着一个

    异步函数串行队列:

    • 开启一条心线程
    • 任务一个接着一个

    异步函数并发队列:

    • 开启线程,并在当前线程执行任务
    • 任务异步执行,没有顺序,CPU调度有关

    1.4 死锁

    • 在主线程使用同步函数等着执行任务
    • 主队列等着主线程的任务执行完毕在执行自己的任务
    • 主队列和主线程相互等待就会造成死锁

    2. 队列的底层原理

    我们下载一份新的libdispath源码进行查看,此处下载的是libdispatch-1173.40.5

    2.1 主队列

    我们全局搜索dispatch_get_main_queue(,加个(是为了方便查找,剔除无关项。通过搜索结果我们可以筛选到如下代码:

    dispatch_queue_main_t
    dispatch_get_main_queue(void)
    {
        return DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q);
    }
    

    我们可以看到主队列的底层实现是由一个DISPATCH_GLOBAL_OBJECT的宏,传入了dispatch_queue_main_t_dispatch_main_q两个参数,下面我们在全局搜索一下这个宏。(如果用Visual Studio Code查看可以直接跳转)

    全局搜索后可以得到下面三个结果:

    // OS_OBJECT_USE_OBJC
    #define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))
    
    // defined(__cplusplus) && !defined(__DISPATCH_BUILDING_DISPATCH__)
    #define DISPATCH_GLOBAL_OBJECT(type, object) (static_cast<type>(&(object)))
    
    // /* Plain C */
    #define DISPATCH_GLOBAL_OBJECT(type, object) ((type)&(object))
    

    分别对应注释中的环境,在此处我们用的是第三个,纯C 语言环境(如果使用VSCode也会直接跳转到这行)。

    我们试着再去点击dispatch_queue_main_t进行跳转,代码如下:

    #if defined(__DISPATCH_BUILDING_DISPATCH__) && !defined(__OBJC__)
    typedef struct dispatch_queue_static_s *dispatch_queue_main_t;
    #else
    DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);
    #endif
    

    对于objc环境是else里面的DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial);DISPATCH_DECL_SUBCLASS这个宏定义如下

    #if OS_OBJECT_SWIFT3
    #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, dispatch_object)
    #define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS_SWIFT(name, base)
    #else // OS_OBJECT_SWIFT3
    #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
    #define DISPATCH_DECL_SUBCLASS(name, base) OS_OBJECT_DECL_SUBCLASS(name, base)
    

    这里选择后面这个非Swift3的,继续查找OS_OBJECT_DECL_SUBCLASS定义如下:

    #define OS_OBJECT_DECL_SUBCLASS(name, super) \
            OS_OBJECT_DECL_IMPL(name, <OS_OBJECT_CLASS(super)>)
    

    这里就是一个IMP指针了,应该是系统定义好的,我们可以通过指针去取值。其实我们也可以看得出来主队列就是serial的子类,一个特殊的串行队列。

    2.2 全局队列

    老样子,我们全局搜索dispatch_get_global_queue(,最后通过筛选找到如下代码:

    dispatch_queue_global_t
    dispatch_get_global_queue(long priority, unsigned long flags)
    {
        dispatch_assert(countof(_dispatch_root_queues) ==
                DISPATCH_ROOT_QUEUE_COUNT);
    
        if (flags & ~(unsigned long)DISPATCH_QUEUE_OVERCOMMIT) {
            return DISPATCH_BAD_INPUT;
        }
        dispatch_qos_t qos = _dispatch_qos_from_queue_priority(priority);
    #if !HAVE_PTHREAD_WORKQUEUE_QOS
        if (qos == QOS_CLASS_MAINTENANCE) {
            qos = DISPATCH_QOS_BACKGROUND;
        } else if (qos == QOS_CLASS_USER_INTERACTIVE) {
            qos = DISPATCH_QOS_USER_INITIATED;
        }
    #endif
        if (qos == DISPATCH_QOS_UNSPECIFIED) {
            return DISPATCH_BAD_INPUT;
        }
        return _dispatch_get_root_queue(qos, flags & DISPATCH_QUEUE_OVERCOMMIT);
    }
    

    可以看出该处代码就是一层封装,进行一些容错处理,然后获取到优先级(qos),最后调用了_dispatch_get_root_queue

    _dispatch_get_root_queue 源码:

    static inline dispatch_queue_global_t
    _dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
    {
        if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
            DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
        }
        return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
    }
    

    我们可以在_dispatch_get_root_queue中看出首先是对优先级的一个容错判断,然后从一个数组中取出一个队列返回。那么我们就来看看这个数组,搜索很麻烦,我用VSCode跳转找到的。源码如下:

    struct dispatch_queue_global_s _dispatch_root_queues[] = {
    #define _DISPATCH_ROOT_QUEUE_IDX(n, flags) \
            ((flags & DISPATCH_PRIORITY_FLAG_OVERCOMMIT) ? \
            DISPATCH_ROOT_QUEUE_IDX_##n##_QOS_OVERCOMMIT : \
            DISPATCH_ROOT_QUEUE_IDX_##n##_QOS)
    #define _DISPATCH_ROOT_QUEUE_ENTRY(n, flags, ...) \
        [_DISPATCH_ROOT_QUEUE_IDX(n, flags)] = { \
            DISPATCH_GLOBAL_OBJECT_HEADER(queue_global), \
            .dq_state = DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE, \
            .do_ctxt = _dispatch_root_queue_ctxt(_DISPATCH_ROOT_QUEUE_IDX(n, flags)), \
            .dq_atomic_flags = DQF_WIDTH(DISPATCH_QUEUE_WIDTH_POOL), \
            .dq_priority = flags | ((flags & DISPATCH_PRIORITY_FLAG_FALLBACK) ? \
                    _dispatch_priority_make_fallback(DISPATCH_QOS_##n) : \
                    _dispatch_priority_make(DISPATCH_QOS_##n, 0)), \
            __VA_ARGS__ \
        }
        _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, 0,
            .dq_label = "com.apple.root.maintenance-qos",
            .dq_serialnum = 4,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(MAINTENANCE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.maintenance-qos.overcommit",
            .dq_serialnum = 5,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, 0,
            .dq_label = "com.apple.root.background-qos",
            .dq_serialnum = 6,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(BACKGROUND, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.background-qos.overcommit",
            .dq_serialnum = 7,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, 0,
            .dq_label = "com.apple.root.utility-qos",
            .dq_serialnum = 8,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(UTILITY, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.utility-qos.overcommit",
            .dq_serialnum = 9,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
            .dq_label = "com.apple.root.default-qos",
            .dq_serialnum = 10,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT,
                DISPATCH_PRIORITY_FLAG_FALLBACK | DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.default-qos.overcommit",
            .dq_serialnum = 11,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, 0,
            .dq_label = "com.apple.root.user-initiated-qos",
            .dq_serialnum = 12,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(USER_INITIATED, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.user-initiated-qos.overcommit",
            .dq_serialnum = 13,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, 0,
            .dq_label = "com.apple.root.user-interactive-qos",
            .dq_serialnum = 14,
        ),
        _DISPATCH_ROOT_QUEUE_ENTRY(USER_INTERACTIVE, DISPATCH_PRIORITY_FLAG_OVERCOMMIT,
            .dq_label = "com.apple.root.user-interactive-qos.overcommit",
            .dq_serialnum = 15,
        ),
    };
    

    可以看到这个数组中定义了很多队列供我们使用,根据优先级和overcommit的值通过相关计算得出需要取得数组中第几个队列。

    下面我们通过代码验证一下:

    验证代码:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"global queue: %@", [NSThread currentThread]);
    });
    
    调用堆栈.jpg

    我们可以从调用堆栈里看出,该代码执行线程所用的队列是com.apple.root.default-qos (concurrent),对应数组中如下:

    _DISPATCH_ROOT_QUEUE_ENTRY(DEFAULT, DISPATCH_PRIORITY_FLAG_FALLBACK,
            .dq_label = "com.apple.root.default-qos",
            .dq_serialnum = 10,
        )
    

    当我们把优先级换成DISPATCH_QUEUE_PRIORITY_BACKGROUND结果如下:队列为com.apple.root.background-qos (concurrent)

    调用堆栈.jpg

    其实我们会有个问题?我们不是在探索队列的底层原理吗?也就是在找队列是如何创建的,那么我们从数组中取出的队列是如何创建的呢?我们只能搜索_dispatch_root_queues了,好在不是很多,经过我们一个一个的排查,最后找到了_dispatch_introspection_init函数,在这里通过循环遍历调用_dispatch_trace_queue_create函数一个一个的取出数组里的地址指针创建的队列。这里还有个小插曲,我一开始找到了一个_dispatch_root_queues_init函数,其内部调用了_dispatch_root_queues_init_once函数,内部也有个类似的循环取出_dispatch_root_queues数组中元素去init的操作,但是仔细一看这里并不是创建队列,而是调用_dispatch_root_queue_init_pthread_pool为每个队列初始化线程池。

    _dispatch_introspection_init源码(创建数组中的队列):

    void
    _dispatch_introspection_init(void)
    {
        _dispatch_introspection.debug_queue_inversions =
                _dispatch_getenv_bool("LIBDISPATCH_DEBUG_QUEUE_INVERSIONS", false);
    
        // Hack to determine queue TSD offset from start of pthread structure
        uintptr_t thread = _dispatch_thread_self();
        thread_identifier_info_data_t tiid;
        mach_msg_type_number_t cnt = THREAD_IDENTIFIER_INFO_COUNT;
        kern_return_t kr = thread_info(pthread_mach_thread_np((void*)thread),
                THREAD_IDENTIFIER_INFO, (thread_info_t)&tiid, &cnt);
        if (!dispatch_assume_zero(kr)) {
            _dispatch_introspection.thread_queue_offset =
                    (void*)(uintptr_t)tiid.dispatch_qaddr - (void*)thread;
        }
        _dispatch_thread_key_create(&dispatch_introspection_key,
                _dispatch_introspection_thread_remove);
        _dispatch_introspection_thread_add(); // add main thread
    
        for (size_t i = 0; i < DISPATCH_ROOT_QUEUE_COUNT; i++) {
            _dispatch_trace_queue_create(&_dispatch_root_queues[i]);
        }
    #if DISPATCH_USE_MGR_THREAD && DISPATCH_USE_PTHREAD_ROOT_QUEUES
        _dispatch_trace_queue_create(_dispatch_mgr_q.do_targetq);
    #endif
        _dispatch_trace_queue_create(&_dispatch_main_q);
        _dispatch_trace_queue_create(&_dispatch_mgr_q);
    }
    

    _dispatch_trace_queue_create的实现我们放在后面在进一步分析。

    我们搜索_dispatch_introspection_init可以发现他的调用是在libdispatch_init函数中,在这个函数中还调用了如下的初始化函数

    _dispatch_hw_config_init();
    _dispatch_time_init();
    _dispatch_vtable_init();
    _os_object_init();
    _voucher_init();
    _dispatch_introspection_init();
    

    2.3 自我创建的队列

    我们知道队列分为串行和并行,那么我们就分别创建下,然后打印一下他们的信息。也顺带打印了一下mainQueueglobalQueue

    po打印.jpg
    名称 类型 target width
    main mian com.apple.root.default-qos.overcommit 0x1
    global global \ 0xfff
    serial serial com.apple.root.default-qos.overcommit 0x1
    concurrent concurrent com.apple.root.default-qos 0xffe

    我们可以看到不同队列对应的都是OS_dispatch_queue_XXX类型,其中主队列和serial队列的target是一样的,在width方面主队列与serial队列依旧一致,可以说主队列是一个特殊的串行队列;global的值是0xfff,concurrent的值是0xffe相对于global少了一个,那么为什么会这样呢?我们马上开始探索。

    首先我们全局搜索一下dispatch_queue_create(const,来看看dispatch_queue_create是如何实现的。

    dispatch_queue_create源码:

    dispatch_queue_t
    dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
    {
        return _dispatch_lane_create_with_target(label, attr,
                DISPATCH_TARGET_QUEUE_DEFAULT, true);
    }
    

    我们发现dispatch_queue_create就是一个隔离的操作,队列的创建实际上是由_dispatch_lane_create_with_target函数来实现的,大概有120多行代码。这里就不上代码了,感兴趣的可以下载源码去查看,我们挑重点来分析。


    dispatch_queue_attr_info_t dqai = _dispatch_queue_attr_to_info(dqa);
    

    首先是上面这行代码,一进来就通过传入的并行和串行的参数初始化一个dispatch_queue_attr_info_t类型的dqai。那么dispatch_queue_attr_info_t是个什么东西呢?我们可以通过全局搜索或者VSCode跳转进行查看。

    struct dispatch_queue_attr_s {
        OS_OBJECT_STRUCT_HEADER(dispatch_queue_attr);
    };
    
    typedef struct dispatch_queue_attr_info_s {
        dispatch_qos_t dqai_qos : 8;
        int      dqai_relpri : 8;
        uint16_t dqai_overcommit:2;
        uint16_t dqai_autorelease_frequency:2;
        uint16_t dqai_concurrent:1;
        uint16_t dqai_inactive:1;
    } dispatch_queue_attr_info_t;
    

    通过查看我们可以知道dispatch_queue_attr_info_t是一个结构体位域,结构体位域可以通过位运算得到我们想要的内容,过滤掉我们不想要的数据。下面我们在来看看这个结构体是如何创建的吧。

    _dispatch_queue_attr_to_info源码:

    dispatch_queue_attr_info_t
    _dispatch_queue_attr_to_info(dispatch_queue_attr_t dqa)
    {
        dispatch_queue_attr_info_t dqai = { };
    
        if (!dqa) return dqai;
    
    #if DISPATCH_VARIANT_STATIC
        if (dqa == &_dispatch_queue_attr_concurrent) {
            dqai.dqai_concurrent = true;
            return dqai;
        }
    #endif
    
        if (dqa < _dispatch_queue_attrs ||
                dqa >= &_dispatch_queue_attrs[DISPATCH_QUEUE_ATTR_COUNT]) {
            DISPATCH_CLIENT_CRASH(dqa->do_vtable, "Invalid queue attribute");
        }
    
        size_t idx = (size_t)(dqa - _dispatch_queue_attrs);
    
        dqai.dqai_inactive = (idx % DISPATCH_QUEUE_ATTR_INACTIVE_COUNT);
        idx /= DISPATCH_QUEUE_ATTR_INACTIVE_COUNT;
    
        dqai.dqai_concurrent = !(idx % DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT);
        idx /= DISPATCH_QUEUE_ATTR_CONCURRENCY_COUNT;
    
        dqai.dqai_relpri = -(int)(idx % DISPATCH_QUEUE_ATTR_PRIO_COUNT);
        idx /= DISPATCH_QUEUE_ATTR_PRIO_COUNT;
    
        dqai.dqai_qos = idx % DISPATCH_QUEUE_ATTR_QOS_COUNT;
        idx /= DISPATCH_QUEUE_ATTR_QOS_COUNT;
    
        dqai.dqai_autorelease_frequency =
                idx % DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
        idx /= DISPATCH_QUEUE_ATTR_AUTORELEASE_FREQUENCY_COUNT;
    
        dqai.dqai_overcommit = idx % DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
        idx /= DISPATCH_QUEUE_ATTR_OVERCOMMIT_COUNT;
    
        return dqai;
    }
    

    _dispatch_queue_attr_to_info源码中我们不难看出对于串行队列也就dqaNULL的时候直接返回一个空的的dqai,其中有一个idx是苹果的一种算法得到的一个值,用于对后续的结构体进行一系列的默认配置和赋值,这里我们着重关注一下dqai.dqai_concurrent,对于并行队列它是有值的,后面会用到。取得dqai后,我们回到_dispatch_lane_create_with_target函数继续分析。经过一系列的容错分析后我们来到一处具有分水岭意义的代码:

    const void *vtable;
        dispatch_queue_flags_t dqf = legacy ? DQF_MUTABLE : 0;
        if (dqai.dqai_concurrent) {
            vtable = DISPATCH_VTABLE(queue_concurrent);
        } else {
            vtable = DISPATCH_VTABLE(queue_serial);
        }
    

    也就是我们上面提到dqai.dqai_concurrent如果有值就创建并行队列,没值就是串行队列。这里通过对vtable赋不同的值来区分,然后通过vtable开辟内存,生成dq(dispatch queue),代码如下:

    dispatch_lane_t dq = _dispatch_object_alloc(vtable,
                sizeof(struct dispatch_lane_s));
    

    然后通过_dispatch_queue_init函数进一步初始化我们的dq。

    _dispatch_queue_init(dq, dqf, dqai.dqai_concurrent ?
                DISPATCH_QUEUE_WIDTH_MAX : 1, DISPATCH_QUEUE_ROLE_INNER |
                (dqai.dqai_inactive ? DISPATCH_QUEUE_INACTIVE : 0));
    

    我们可以看到_dispatch_queue_init的第三参数在上面的代码中通过一个三目运算取不同的值进行传入,判断条件就是dqai.dqai_concurrent,其实就是串行队列取1并行队列是个宏DISPATCH_QUEUE_WIDTH_MAX,我们跳转到这个宏(VSCode跳转的),代码如下:

    #define DISPATCH_QUEUE_WIDTH_FULL_BIT       0x0020000000000000ull
    #define DISPATCH_QUEUE_WIDTH_FULL           0x1000ull
    #define DISPATCH_QUEUE_WIDTH_POOL (DISPATCH_QUEUE_WIDTH_FULL - 1)
    #define DISPATCH_QUEUE_WIDTH_MAX  (DISPATCH_QUEUE_WIDTH_FULL - 2)
    #define DISPATCH_QUEUE_USES_REDIRECTION(width) \
            ({ uint16_t _width = (width); \
            _width > 1 && _width < DISPATCH_QUEUE_WIDTH_POOL; })
    

    可以看到这个值跟我们上面打印的width的值不谋而合,对于全局并发队列是0x1000-1 = 0xfff,对于自定义的并发队列值是0x1000-2 = 0xffe。如果想要验证DISPATCH_QUEUE_WIDTH_POOL也就是0x1000-1 = 0xfff是globalwidth可以直接全局搜索验证,这里就不多说了。

    我们继续往下看,_dispatch_queue_init的第四个参数是初始状态位,因为队列是有状态的就是在这里初始化的。接下来就是将label的值和优先级``赋值给dq,以及对队列的是否是就绪状态是对优先级的处理,还有不是补不活跃状态的队列也要进一步处理,代码如下:

    dq->dq_label = label;
    dq->dq_priority = _dispatch_priority_make((dispatch_qos_t)dqai.dqai_qos,
            dqai.dqai_relpri);
    if (overcommit == _dispatch_queue_attr_overcommit_enabled) {
        dq->dq_priority |= DISPATCH_PRIORITY_FLAG_OVERCOMMIT;
    }
    if (!dqai.dqai_inactive) {
        _dispatch_queue_priority_inherit_from_target(dq, tq);
        _dispatch_lane_inherit_wlh_from_target(dq, tq);
    }
    

    接下来我们看到如下代码:

    _dispatch_retain(tq);
    dq->do_targetq = tq;
    

    这个tq是传入的,这里我们传入的时候是DISPATCH_TARGET_QUEUE_DEFAULT也就是NULL,那么这里的tq是在哪里赋值的呢?我们回过头去找一找,在代码中很多判断tq的地方,但都因为tqNULL所以都没有进入相关分支,经过我们查找,在如下代码处给tq赋了值

    if (!tq) {
            tq = _dispatch_get_root_queue(
                    qos == DISPATCH_QOS_UNSPECIFIED ? DISPATCH_QOS_DEFAULT : qos,
                    overcommit == _dispatch_queue_attr_overcommit_enabled)->_as_dq;
            if (unlikely(!tq)) {
                DISPATCH_CLIENT_CRASH(qos, "Invalid queue attribute");
            }
        }
    

    _dispatch_get_root_queue源码:

    static inline dispatch_queue_global_t
    _dispatch_get_root_queue(dispatch_qos_t qos, bool overcommit)
    {
        if (unlikely(qos < DISPATCH_QOS_MIN || qos > DISPATCH_QOS_MAX)) {
            DISPATCH_CLIENT_CRASH(qos, "Corrupted priority");
        }
        return &_dispatch_root_queues[2 * (qos - 1) + overcommit];
    }
    

    还是上面global queue时的代码,也是从_dispatch_root_queues数组中取出的值,所以说tq中记录了dq_label的值,也就是我们一开始打印时的target的值。

    知道tq是如何取值后我们继续分析,最后调用了return _dispatch_trace_queue_create(dq)._dq;返回创建的队列,这个函数在我们讲解global queue的时候,对于_dispatch_root_queues数组中的队列创建的时候也是最终调用了该函数,那么我们就接着看看这个函数都做了什么事情吧。

    DISPATCH_ALWAYS_INLINE
    static inline dispatch_queue_class_t
    _dispatch_trace_queue_create(dispatch_queue_class_t dqu)
    {
        _dispatch_only_if_ktrace_enabled({
            uint64_t dq_label[4] = {0}; // So that we get the right null termination
            dispatch_queue_t dq = dqu._dq;
            strncpy((char *)dq_label, (char *)dq->dq_label ?: "", sizeof(dq_label));
    
            _dispatch_ktrace2(DISPATCH_QOS_TRACE_queue_creation_start,
                    dq->dq_serialnum,
                    _dispatch_priority_to_pp_prefer_fallback(dq->dq_priority));
    
            _dispatch_ktrace4(DISPATCH_QOS_TRACE_queue_creation_end,
                            dq_label[0], dq_label[1], dq_label[2], dq_label[3]);
        });
    
        return _dispatch_introspection_queue_create(dqu);
    }
    

    _dispatch_trace_queue_create源码中,主要分两部分,一部分是系统调试,另一部分是调用_dispatch_introspection_queue_create函数。调试就不看了,下面我们就来看看_dispatch_introspection_queue_create函数的内容。

    _dispatch_introspection_queue_create源码:

    dispatch_queue_class_t
    _dispatch_introspection_queue_create(dispatch_queue_t dq)
    {
        dispatch_queue_introspection_context_t dqic;
        size_t sz = sizeof(struct dispatch_queue_introspection_context_s);
    
        if (!_dispatch_introspection.debug_queue_inversions) {
            sz = offsetof(struct dispatch_queue_introspection_context_s,
                    __dqic_no_queue_inversion);
        }
        dqic = _dispatch_calloc(1, sz);
        dqic->dqic_queue._dq = dq;
        if (_dispatch_introspection.debug_queue_inversions) {
            LIST_INIT(&dqic->dqic_order_top_head);
            LIST_INIT(&dqic->dqic_order_bottom_head);
        }
        dq->do_finalizer = dqic;
    
        _dispatch_unfair_lock_lock(&_dispatch_introspection.queues_lock);
        LIST_INSERT_HEAD(&_dispatch_introspection.queues, dqic, dqic_list);
        _dispatch_unfair_lock_unlock(&_dispatch_introspection.queues_lock);
    
        DISPATCH_INTROSPECTION_INTERPOSABLE_HOOK_CALLOUT(queue_create, dq);
        if (DISPATCH_INTROSPECTION_HOOK_ENABLED(queue_create)) {
            _dispatch_introspection_queue_create_hook(dq);
        }
        return upcast(dq)._dqu;
    }
    

    dispatch_queue_introspection_context_t & dispatch_queue_introspection_context_s 结构体

    typedef struct dispatch_queue_introspection_context_s {
        dispatch_queue_class_t dqic_queue;
        dispatch_function_t dqic_finalizer;
        LIST_ENTRY(dispatch_queue_introspection_context_s) dqic_list;
    
        char __dqic_no_queue_inversion[0];
    
        // used for queue inversion debugging only
        dispatch_unfair_lock_s dqic_order_top_head_lock;
        dispatch_unfair_lock_s dqic_order_bottom_head_lock;
        LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_top_head;
        LIST_HEAD(, dispatch_queue_order_entry_s) dqic_order_bottom_head;
    } *dispatch_queue_introspection_context_t;
    

    _dispatch_introspection_queue_create函数中

    1. 首先定义了一个dispatch_queue_introspection_context_t的上下文。代码也放在上面了。
    2. 然后获取了dispatch_queue_introspection_context_s结构体占用内存的大小。
    3. 然后判断非自我观察调试队列倒置(翻译过来的,我也不知道啥意思),反正就是这种情况下对上一步取出的sz偏移
    4. 然后调用_dispatch_calloc函数分配内存
    5. 将传入的dq赋值给一开始定义的dqicdqic_queue._dq
    6. 还是3中的那个判断,开始监听队列的头尾(这里应该是入队和出队)时添加任务到队列尾,从队列头取出任务开始执行(个人猜想)
    7. dqic赋值给传入的dqdo_finalizer(终结器,应该是终止队列时使用的)
    8. 加锁插入创建的队列到系统管理的队列里面?在解锁
    9. 一些hook处理(不是很明白)
    10. 返回

    由于能力有限,此处分析的乱七八糟,还请大神们指点。,其实到这里也就基本分析完了队列的整个创建过程,在不同操作系统,对于不同架构的CPU,队列的差别肯定是有的,但是原理应该是一样的。感觉分析libdispath的源码还是挺难的,比较队列线程这种东西属于很底层的东西了。

    3. 一些定义

    3.1 dispatch_queue_t

    分析了半天,其实我们还没有仔细的分析dispatch_queue_t到底是个什么东西呢 ?

    我们点击跳转后跳转到DISPATCH_DECL(dispatch_queue);这样一行代码处,这显然不是我们想要的。这个时候我们可以看看我们的创建队列的方法时返回值处的代码return _dispatch_trace_queue_create(dq)._dq;这个_dq就是我们要返回的值,前面的_dispatch_trace_queue_create函数的返回值类型是dispatch_queue_class_t定义如下:

    // Dispatch queue cluster class: type for any dispatch_queue_t
    typedef union {
        struct dispatch_queue_s *_dq;
        struct dispatch_workloop_s *_dwl;
        struct dispatch_lane_s *_dl;
        struct dispatch_queue_static_s *_dsq;
        struct dispatch_queue_global_s *_dgq;
        struct dispatch_queue_pthread_root_s *_dpq;
        struct dispatch_source_s *_ds;
        struct dispatch_channel_s *_dch;
        struct dispatch_mach_s *_dm;
        dispatch_lane_class_t _dlu;
    #ifdef __OBJC__
        id<OS_dispatch_queue> _objc_dq;
    #endif
    } dispatch_queue_class_t DISPATCH_TRANSPARENT_UNION;
    

    这是个联合体,成员_dqdispatch_queue_s类型,其定义如下:

    struct dispatch_queue_s {
        DISPATCH_QUEUE_CLASS_HEADER(queue, void *__dq_opaque1);
        /* 32bit hole on LP64 */
    } DISPATCH_ATOMIC64_ALIGN;
    

    接下来我们继续看DISPATCH_QUEUE_CLASS_HEADER,这是个宏,定义如下:

    #define DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__) \
        _DISPATCH_QUEUE_CLASS_HEADER(x, __pointer_sized_field__); \
        /* LP64 global queue cacheline boundary */ \
        unsigned long dq_serialnum; \
        const char *dq_label; \
        DISPATCH_UNION_LE(uint32_t volatile dq_atomic_flags, \
            const uint16_t dq_width, \
            const uint16_t __dq_opaque2 \
        ); \
        dispatch_priority_t dq_priority; \
        union { \
            struct dispatch_queue_specific_head_s *dq_specific_head; \
            struct dispatch_source_refs_s *ds_refs; \
            struct dispatch_timer_source_refs_s *ds_timer_refs; \
            struct dispatch_mach_recv_refs_s *dm_recv_refs; \
            struct dispatch_channel_callbacks_s const *dch_callbacks; \
        }; \
        int volatile dq_sref_cnt
    

    这个宏定义了一些通用属性,包含队列的所有成员,例如编号、label、队列宽度、优先级等等。

    那么回到问题本身,我们的dispatch_queue_t是在什么时候定义的呢?我们尝试搜索了一下dispatch_queue_t;(其实也搜索了dispatch_source_s前后都有空格)找到如下代码:

    typedef struct dispatch_continuation_s *dispatch_continuation_t;
    typedef struct dispatch_queue_s *dispatch_queue_t;
    typedef struct dispatch_source_s *dispatch_source_t;
    typedef struct dispatch_group_s *dispatch_group_t;
    typedef struct dispatch_object_s *dispatch_object_t;
    

    在这里我们可以清楚的看到dispatch_queue_t是一个dispatch_queue_s类型的结构体指针。至此我们就找到了dispatch_queue_t的定义。

    3.2 dispatch_object_t

    在探索过程中我们还多次看到了dispatch_object_t的身影,其实它的定义就在dispatch_queue_class_t的下面,如果我们直接搜索dispatch_object_t会看到很多关于它的定义,为什么我会认为dispatch_queue_class_t下面代码处使我们想要的呢?因为有个#ifndef __OBJC__

    dispatch_object_t定义:

    typedef union {
        struct _os_object_s *_os_obj;
        struct dispatch_object_s *_do;
        struct dispatch_queue_s *_dq;
        struct dispatch_queue_attr_s *_dqa;
        struct dispatch_group_s *_dg;
        struct dispatch_source_s *_ds;
        struct dispatch_channel_s *_dch;
        struct dispatch_mach_s *_dm;
        struct dispatch_mach_msg_s *_dmsg;
        struct dispatch_semaphore_s *_dsema;
        struct dispatch_data_s *_ddata;
        struct dispatch_io_s *_dchannel;
    
        struct dispatch_continuation_s *_dc;
        struct dispatch_sync_context_s *_dsc;
        struct dispatch_operation_s *_doperation;
        struct dispatch_disk_s *_ddisk;
        struct dispatch_workloop_s *_dwl;
        struct dispatch_lane_s *_dl;
        struct dispatch_queue_static_s *_dsq;
        struct dispatch_queue_global_s *_dgq;
        struct dispatch_queue_pthread_root_s *_dpq;
        dispatch_queue_class_t _dqu;
        dispatch_lane_class_t _dlu;
        uintptr_t _do_value;
    } dispatch_object_t DISPATCH_TRANSPARENT_UNION;
    

    我们发现dispatch_object_t是一个联合体,使用联合体的成员互斥性,同时只存在一种类型带来了多态性。我们可以用一个dispatch_object_tGCD中表示所有的类型。

    4. 总结

    至此我们的GCD队列篇就分析完毕了下面我们稍作总结

    1. 在iOS中可以获取主队列,全局并行队列,还可以自己创建队列,自己创建队列可以是串行的也可以是并行;
    2. 主队列和串行队列的宽度都是1,全局并行队列的宽度是0x1000-1 = 0xfff,自己创建的并行队列的宽度是0x1000-2 = 0xffe;
    3. 主队列底层是由DISPATCH_GLOBAL_OBJECT(dispatch_queue_main_t, _dispatch_main_q)获取到的,暂时没有找到其开源的具体实现
    4. 全局并发队列是从_dispatch_root_queues数组中取出的
    5. 关于自我创建的队列也是从_dispatch_root_queues数组中取出一个对应的值,赋值给tq,然后创建响应的队列
    6. 关于_dispatch_root_queues数组中的队列是在_dispatch_introspection_init函数中循环遍历创建的,_dispatch_introspection_init是由libdispatch_init函数调用的。

    相关文章

      网友评论

        本文标题:iOS Objective-C GCD之queue(队列)篇

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