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

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函数调用的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336