变态的libDispatch源码分析-全局队列异步延时任务处理过程-原理与创建ds

13 篇文章 0 订阅
8 篇文章 0 订阅

延时任务的处理通常包括如下几个要素:

a). 任务;

b). 延时任务队列;

c). 计时器,循环检测延时任务队列,若有任务到时则取出,然后执行;


下面分析下GCD中关于libdispatch的延时任务处理方式。

下面这部分代码是根据自身需求,封装的一个接口,基本能满足异步延时任务的处理:

void _dispatch_delayed(dispatch_time_t when,
    dispatch_queue_t queue, WorkItem* item) {
    uint64_t delta;

    dispatch_source_t ds = NULL;

    if (when == DISPATCH_TIME_FOREVER) {
        return;
    }
//封装source
    ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    if (!ds) {
        return;
    }
//创建延时ContextForTimer
    ContextForTimer* Context = new ContextForTimer(item, ds);
    if (!Context) {
        dispatch_release(ds);
        return;
    }

//设置时间参数
    dispatch_set_context(ds, reinterpret_cast<void*>(Context));
//封装并派遣“为ds设置/取消计时器“任务
    dispatch_source_set_event_handler_f(ds, _dispatch_after_timer_callback);
    dispatch_source_set_cancel_handler_f(ds, _dispatch_after_timer_cancel);
//派遣ds任务
    dispatch_source_set_timer(ds, when, 0, 0);
//若ds插入队列,对理解并没有唤醒,resume将起到队列唤醒的左右
    dispatch_resume(ds);
}

1. 创建Source

延时任务的几个基本要素:任务,时间,队列,都有了,

先封装一个_dispatch_source_t;

用途: 存储延时任务相关的一个队列,这个队列用于调度延时任务相关的一些操作,比如为任务设置时间参数;

    ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
	uintptr_t handle,
	unsigned long mask,
	dispatch_queue_t q)
{
	dispatch_source_t ds = NULL;
	static char source_label[sizeof(ds->dq_label)] = "source";

	// input validation
	if (type == NULL || (mask & ~type->mask)) {
		goto out_bad;
	}

	ds = calloc(1ul, sizeof(struct dispatch_source_s));
	if (slowpath(!ds)) {
		goto out_bad;
	}

	// Initialize as a queue first, then override some settings below.
	_dispatch_queue_init((dispatch_queue_t)ds);
	memcpy(ds->dq_label, source_label, sizeof(source_label));

	// Dispatch Object
	ds->do_vtable = &_dispatch_source_kevent_vtable;
	ds->do_ref_cnt++; // the reference the manger queue holds
	ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;
	// do_targetq will be retained below, past point of no-return
	ds->do_targetq = q;

	if (slowpath(!type->init(ds, type, handle, mask, q))) {
		goto out_bad;
	}

	dispatch_assert(!(ds->ds_is_level && ds->ds_is_adder));
#if DISPATCH_DEBUG
	dispatch_debug(ds, __FUNCTION__);
#endif

	_dispatch_retain(ds->do_targetq);
	return ds;
	
out_bad:
	free(ds);
	return NULL;
}

(1). 创建并初始化source


source结构

struct dispatch_source_s {
	DISPATCH_STRUCT_HEADER(dispatch_source_s, dispatch_source_vtable_s);
	DISPATCH_QUEUE_HEADER;
	// Instruments always copies DISPATCH_QUEUE_MIN_LABEL_SIZE, which is 64,
	// so the remainder of the structure must be big enough
	union {
		char _ds_pad[DISPATCH_QUEUE_MIN_LABEL_SIZE];
		struct {
			char dq_label[8];
			dispatch_kevent_t ds_dkev;
			
			dispatch_function_t ds_handler_func;
			void *ds_handler_ctxt;
			
			void *ds_cancel_handler;
			
			unsigned int ds_is_level:1,
			ds_is_adder:1,
			ds_is_installed:1,
			ds_needs_rearm:1,
			ds_is_armed:1,
			ds_is_legacy:1,
			ds_cancel_is_block:1,
			ds_handler_is_block:1;

			unsigned int ds_atomic_flags;

			unsigned long ds_data;
			unsigned long ds_pending_data;
			unsigned long ds_pending_data_mask;
			
			TAILQ_ENTRY(dispatch_source_s) ds_list;
			
			unsigned long ds_ident_hack;
			
			struct dispatch_timer_source_s ds_timer;
		};
	};
};

初始化:

ds = calloc(1ul, sizeof(struct dispatch_source_s));

_dispatch_queue_init((dispatch_queue_t)ds);

inline void _dispatch_queue_init(dispatch_queue_t dq)
{
    dq->do_vtable = &_dispatch_queue_vtable;
    dq->do_next = DISPATCH_OBJECT_LISTLESS;
    dq->do_ref_cnt = 1;
    dq->do_xref_cnt = 1;
    dq->do_targetq = _dispatch_get_root_queue(0, true);
    dq->dq_running = 0;
    dq->dq_width = 1;
    dq->dq_serialnum = dispatch_atomic_inc(&_dispatch_queue_serial_numbers) - 1;
}
这里初始化调用的是_dispatch_queue_init((dispatch_queue_t)ds); 也就是将ds左右一个dq对象来初始化,其实无所谓,这只是初始化,只要保证所有变量都初始化就好,谁叫人家是透明联合体呢。

需要注意的有下面几个点:

dq->do_vtable = &_dispatch_queue_vtable; 这是特殊的vtable,但是这个是不算数,后面会根据自身条件再设置的;
dq->do_targetq = _dispatch_get_root_queue(0, true); 通常默认的初始化目标队列是root_queue(0,true);
dq->dq_running = 0;

看看这个vtable是什么:接下来根据传递过来的参数初始化ds:
	// Dispatch Object
	ds->do_vtable = &_dispatch_source_kevent_vtable;
	ds->do_ref_cnt++; // the reference the manger queue holds
	ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;
	// do_targetq will be retained below, past point of no-return
	ds->do_targetq = q;

#define DISPATCH_OBJECT_SUSPEND_INTERVAL    2u

	ds->do_targetq = q;

把关键信息解释一下:

ds->do_vtable = &_dispatch_source_kevent_vtable; 设置针对source的source_kevent_vtable
ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL; 表明这是个suspend;
ds->do_targetq = q; 这个ds的目标队列就是要传递过来的队列;

(2). 其他初始化

	if (slowpath(!type->init(ds, type, handle, mask, q))) {
		goto out_bad;
	}

dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);


type就是传递的DISPATCH_SOURCE_TYPE_TIMER

位置:libdispatch/src/source_kevent.c
#define DISPATCH_SOURCE_TYPE_TIMER (&_dispatch_source_type_timer)

const struct dispatch_source_type_s _dispatch_source_type_timer = {
	.opaque = (void *)&_dispatch_source_type_timer_ke,
	.mask = DISPATCH_TIMER_INTERVAL|DISPATCH_TIMER_ONESHOT|DISPATCH_TIMER_ABSOLUTE|DISPATCH_TIMER_WALL_CLOCK,
	.init = dispatch_source_type_timer_init,
};
static const struct kevent _dispatch_source_type_timer_ke = {
    .filter = DISPATCH_EVFILT_TIMER,
}; 
因此调用的init就是调用了dispatch_source_type_timer_init
static bool
dispatch_source_type_timer_init(dispatch_source_t ds, dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t q)
{
	if (!dispatch_source_type_kevent_init(ds, type, handle, mask, q)) {
		return false;
	}
	ds->ds_needs_rearm = true;
	ds->ds_timer.flags = mask;
	return true;
}

其中ds就是刚才创建的ds,

type就是DISPATCH_SOURCE_TYPE_TIMER

handle是NULL;

q是最初的dq;

接下来进入到了:

static bool
dispatch_source_type_kevent_init(dispatch_source_t ds, dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t q)
{
	const struct kevent *proto_kev = type->opaque;
	dispatch_kevent_t dk = NULL;

	switch (proto_kev->filter) {
	case EVFILT_SIGNAL:
		if (handle >= NSIG) {
			return false;
		}
		break;
	case EVFILT_FS:
	case DISPATCH_EVFILT_CUSTOM_ADD:
	case DISPATCH_EVFILT_CUSTOM_OR:
	case DISPATCH_EVFILT_TIMER:
		if (handle) {
			return false;
		}
		break;
	default:
		break;
	}

	dk = calloc(1ul, sizeof(struct dispatch_kevent_s));
	if (slowpath(!dk)) {
		return false;
	}

	dk->dk_kevent = *proto_kev;
	dk->dk_kevent.ident = handle;
	dk->dk_kevent.flags |= EV_ADD|EV_ENABLE;
	dk->dk_kevent.fflags |= (uint32_t)mask;
	dk->dk_kevent.udata = dk;
	TAILQ_INIT(&dk->dk_sources);

	// Dispatch Source
	ds->ds_ident_hack = dk->dk_kevent.ident;
	ds->ds_dkev = dk;
	ds->ds_pending_data_mask = dk->dk_kevent.fflags;
	if ((EV_DISPATCH|EV_ONESHOT) & proto_kev->flags) {
		ds->ds_is_level = true;
		ds->ds_needs_rearm = true;
	} else if (!(EV_CLEAR & proto_kev->flags)) {
		// we cheat and use EV_CLEAR to mean a "flag thingy"
		ds->ds_is_adder = true;
	}
	return true;
}

进入到这里来,也许就开始一头雾水了,为什么跟kevent扯上关系了;

但是我们需要知道,在这个init里面为ds设置了一个dk_kevent;

	ds->ds_ident_hack = dk->dk_kevent.ident;
	ds->ds_dkev = dk;
	ds->ds_pending_data_mask = dk->dk_kevent.fflags;
	if ((EV_DISPATCH|EV_ONESHOT) & proto_kev->flags) {
		ds->ds_is_level = true;
		ds->ds_needs_rearm = true;
	} else if (!(EV_CLEAR & proto_kev->flags)) {
		// we cheat and use EV_CLEAR to mean a "flag thingy"
		ds->ds_is_adder = true;
	}


我们先不深究这段代码,第二节中我们来介绍GCD处理延时任务的原理。

记住,这就是source创建的一个过程。


2. GCD处理延时任务的原理

在文章的初始,我讲述了延时任务执行的几个关键因素,下面我给出GCD的处理原理



GCD针对异步延时任务,至少有两个队列,一个事件队列,一个任务队列;

a). 事件队列就是图中指示的_dispatch_mgr_q;

b). 延时任务存放在_dispatch_mgr_q的一个指针指向的队列中,延时任务的插入也是异步的;

c). 事件检测线程在不停地轮询中,做了两件事

  •   检测kevent中的事件,同时通过空等待的方式,消耗需要等待的时间;当事件来临时,将任务从_dispatch_mgr_q取出并invoke;若发现任务处于pending状态,将返回;
  • 轮询检测计时队列中的任务是否到时,若到时了,更新pending_date状态,然后从新激活任务,并执行;

这就是延时任务执行的一个大概原理;


3.  封装并设置计时上下文ContextForTimer


这部分不属于Libdispatch的东西,但是按照libdispatch的原理,需要设置计时到达之后的处理函数,假设为:_dispatch_callback;

我们将

延时任务的workitem放在一个ContextForTimer,任务的时间到达时,执行:

static void
_dispatch_callback(void *ctxt) {

ContextForTimer* Context = reinterpret_cast<ContextForTimer*>(ctxt);
    if (Context) {
       Context->run();
    }
}
用途:ContextForTimer作为_dispatch_callback的一个参数;_dispatch_callback会作为“延时任务”结构中的一个函数指针;当延时任务被调度时,执行的就是_dispatch_callback;ContextForTimer则会作为“延时任务”结构的一个上下文ctxt;

说到这大体了解了ContextForTimer的用途,

创建ContextForTimer内容:

ContextForTimer->item = item;

ContextForTimer->ds = ds;

接下来将Context设置到ds上去:

void
dispatch_set_context(dispatch_object_t dou, void *context)
{
	struct dispatch_object_s *obj = DO_CAST(dou);

	if (obj->do_ref_cnt != DISPATCH_OBJECT_GLOBAL_REFCNT) {
		obj->do_ctxt = context;
	}
}



 
 

4. 设置时间到达后的处理函数和延时任务取消后的处理函数

    dispatch_source_set_event_handler_f(ds, _dispatch_callback);
    dispatch_source_set_cancel_handler_f(ds, _dispatch_cancel);

其中_dispatch_callback和 _dispatch_cancel是我写的函数指针;

而dispatch_source_set_event_handler_f,将这两个指针封装成了两个root队列的任务;

(1). 入口

dispatch_source_set_event_handler_f(ds, _dispatch_callback);

_dispatch_callback只是作为一个handler,设置这个Handler的是 _dispatch_source_set_event_handler_f

void
dispatch_source_set_event_handler_f(dispatch_source_t ds,
	dispatch_function_t handler)
{
	dispatch_assert(!ds->ds_is_legacy);
	dispatch_barrier_async_f((dispatch_queue_t)ds,
		handler, _dispatch_source_set_event_handler_f);
}

看看 _dispatch_source_set_event_handler_f 做了啥子事情:

static void
_dispatch_source_set_event_handler_f(void *context)
{
	dispatch_source_t ds = (dispatch_source_t)_dispatch_queue_get_current();
	dispatch_assert(ds->do_vtable == &_dispatch_source_kevent_vtable);
	
#ifdef __BLOCKS__
	if (ds->ds_handler_is_block && ds->ds_handler_ctxt) {
		Block_release(ds->ds_handler_ctxt);
	}
#endif
	ds->ds_handler_func = context;
	ds->ds_handler_ctxt = ds->do_ctxt;
	ds->ds_handler_is_block = false;
}
原来:

  • ds->ds_handler_func = context; //刚才的_dispatch_callback
  • ds->ds_handler_ctxt = ds->do_ctxt;//封装的ContextForTimer
  • ds->ds_handler_is_block = false;


(b). 继续往下走,看怎么封装并执行_dispatch_source_set_event_handler_f

void
dispatch_barrier_async_f(dispatch_queue_t dq, void *context, dispatch_function_t func)
{
	dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_cacheonly());

	if (!dc) {
		return _dispatch_barrier_async_f_slow(dq, context, func);
	}

	dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
	dc->dc_func = func;
	dc->dc_ctxt = context;

	_dispatch_queue_push(dq, dc);
}

DISPATCH_NOINLINE
static void
_dispatch_barrier_async_f_slow(dispatch_queue_t dq, void *context, dispatch_function_t func)
{
	dispatch_continuation_t dc = fastpath(_dispatch_continuation_alloc_from_heap());

	dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT);
	dc->dc_func = func;
	dc->dc_ctxt = context;

	_dispatch_queue_push(dq, dc);
}

dc->do_vtable = (void *)(DISPATCH_OBJ_ASYNC_BIT | DISPATCH_OBJ_BARRIER_BIT); 表示这诗歌需要执行的任务了,而不是其他结构;


这段代码显示了:GCD如何将_dispatch_source_set_event_handler_f封装到dc;

过程是不是很熟悉,跟上篇的异步任务执行过程,封装->压栈->wakeup一样;

但是接下来有不同的了。

(c). wakeup ds


当程序执行到_dispatch_queue_push_list_slow

DISPATCH_NOINLINE 
void
_dispatch_queue_push_list_slow(dispatch_queue_t dq, struct dispatch_object_s *obj)
{
	// The queue must be retained before dq_items_head is written in order
	// to ensure that the reference is still valid when _dispatch_wakeup is
	// called. Otherwise, if preempted between the assignment to
	// dq_items_head and _dispatch_wakeup, the blocks submitted to the
	// queue may release the last reference to the queue when invoked by
	// _dispatch_queue_drain. <rdar://problem/6932776>
	_dispatch_retain(dq);
	dq->dq_items_head = obj;
	_dispatch_wakeup(dq);
	_dispatch_release(dq);
}
这里的dq是我们之前封装的ds;

而Obj是封装了_dispatch_source_set_event_handler_f的dc;

果断进入到_dispatch_wakeup
// 6618342 Contact the team that owns the Instrument DTrace probe before renaming this symbol
dispatch_queue_t
_dispatch_wakeup(dispatch_object_t dou)
{
	dispatch_queue_t tq;

	if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
		return NULL;
	}
	if (!dx_probe(dou._do) && !dou._dq->dq_items_tail) {
		return NULL;
	}

	if (!_dispatch_trylock(dou._do)) {
		return NULL;
	}
	_dispatch_retain(dou._do);
	tq = dou._do->do_targetq;
	_dispatch_queue_push(tq, dou._do);
	return tq;	// libdispatch doesn't need this, but the Instrument DTrace probe does
}
看看dispatch_source_create在创建ds为ds设置的值:

ds->do_vtable = &_dispatch_source_kevent_vtable;
ds->do_ref_cnt++; // the reference the manger queue holds
ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;
// do_targetq will be retained below, past point of no-return
ds->do_targetq = q;


也就是ds->do_suspend_cnt = DISPATCH_OBJECT_SUSPEND_INTERVAL;

#define DISPATCH_OBJECT_SUSPEND_INTERVAL 2u

因此下面这句代码:

if (slowpath(DISPATCH_OBJECT_SUSPENDED(dou._do))) {
return NULL;
}

#define DISPATCH_OBJECT_SUSPENDED(x) ((x)->do_suspend_cnt >= DISPATCH_OBJECT_SUSPEND_INTERVAL)

返回为true;

即wakeup直接return NULL;没做任何其他事;

(d). 结论


这个流程的结论就是:

dq->dq_items_head = obj;

将obj的任务插入到了dq的头部中;

而之后的 dispatch_source_set_cancel_handler_f(ds, _dispatch_after_timer_cancel); 也就是将新的任务插入在上面ds的head的next的位置,然后return了;

并没有调度ds;

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值