iOS底层 - 一个栅栏函 拦住了 数

1,387 阅读7分钟

这是我参与8月更文挑战的第14天,活动详情查看:8月更文挑战

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)
  23. iOS底层 - 关于死锁,你了解多少?
  24. iOS底层 - 单例 销毁 可否 ?
  25. iOS底层 - Dispatch Source

以上内容的总结专栏


细枝末节整理


前言

在之前的 iOS底层原理探索 之 GCD原理(上) 篇章中,我们重点对 GCD的函数和队列、 GCD的底层数据结构、同步函数底层调用和异步函数底层调用进行了剩入的分析。 而 在 后续的 关于死锁,你了解多少?销毁 一个 单例Dispatch Source 三篇分别对 GCD 使用中导致死锁的问题 、 单例底层实现逻辑 和 Dispatch Source 的应用内容。 今天,我们再来研究一下 GCD 部分的 栅栏函数底层实现,信号量和调度组的应用。 也算是 GCD 篇章的一个结尾。好的,下面就开始今天的内容。

栅栏函数

  • 栅栏函数 最直接的作用 就是 控制 任务执行顺序,同步
  • 栅栏函数 只能控制同一并发队列

dispatch_barrier_async

之前添加的任务执行完毕才会执行函数内部添加的block任务块

dispatch_barrier_sync

之前添加的任务执行完毕才会执行函数内部添加的block任务块, 会堵塞线程,影响后面的任务执行

例子

我们可以用栅栏函数实现控制任务执行的顺序,以解决某些场景下,一个任务的执行依赖于上一个任务执行完成后的结果。例1中栅栏函数的任务执行会在任务1、2执行完成后才会执行

例1

    //自定义并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("superman", DISPATCH_QUEUE_CONCURRENT);

    //任务1.异步函数 
    dispatch_async(concurrentQueue, ^{
        sleep(5);
        NSLog(@"123");
    });
    
    //任务2.异步函数
    dispatch_async(concurrentQueue, ^{
        sleep(3);
        NSLog(@"456");
    });
    
     //任务3. 栅栏函数 */ 
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"----%@-----",[NSThread currentThread]);
    });
    
    // 任务4. 异步函数
    dispatch_async(concurrentQueue, ^{
        NSLog(@"789");
    });
    
    // 任务5
    NSLog(@"**********哎呦不错哦");
    
打印结果如下:

 21:24:17.375649+0800 [18834:145002] **********哎呦不错哦
 21:24:20.379650+0800 [18834:145218] 456
 21:24:22.376071+0800 [18834:145217] 123
 21:24:22.376270+0800 [18834:145217] ----<NSThread: 0x600003c346c0>{number = 7, name = (null)}-----
 21:24:22.376385+0800 [18834:145217] 789
  • 例1中的 dispatch_barrier_async 函数修改为 dispatch_barrier_sync 函数后,其执行顺序变为:
 21:35:35.730836+0800 [18959:155093] 456
 21:35:37.730720+0800 [18959:154791] 123
 21:35:37.731164+0800 [18959:154350] ----<NSThread: 0x600001548540>{number = 1, name = main}-----
 21:35:37.731459+0800 [18959:154350] **********哎呦不错哦
 21:35:37.731521+0800 [18959:154791] 789
  • 例1中的 dispatch_queue_t 换为 dispatch_get_global_queue 全局并发队列之后,任务执行顺序又变为了:
22:16:19.411253+0800 [19499:189314] ----<NSThread: 0x60000343c1c0>{number = 1, name = main}-----
22:16:19.411375+0800 [19499:189314] **********哎呦不错哦
22:16:19.411451+0800 [19499:189527] 789
22:16:22.414475+0800 [19499:189526] 456
22:16:24.415446+0800 [19499:189447] 123

也就是说对于全局并发队列栅栏函数并没有起到作用。

所以,只有我们自定义的并发队列使用栅栏函数才可以达到我们预期的执行顺序。

图解

我们画一个图来说明一下上面的这两种情况吧:

dispatch_barrier_async.001.jpeg

dispatch_barrier_sync.001.jpeg

问题

  • 栅栏函数为什么使用自定义并发队列才可以?
  • 栅栏函数是如何控制执行顺序的? 带着这两个问题我们去深入到源码中探索一下。

dispatch_barrier_sync

void
dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
{
	uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
	if (unlikely(_dispatch_block_has_private_data(work))) {
		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
	}
	_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
}
_dispatch_barrier_sync_f
DISPATCH_NOINLINE
static void
_dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
		dispatch_function_t func, uintptr_t dc_flags)
{
	_dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
}
_dispatch_barrier_sync_f_inline

#define DC_FLAG_BARRIER 0x002ul

...


static inline void
_dispatch_barrier_sync_f_inline(dispatch_queue_t dq, void *ctxt,
		dispatch_function_t func, uintptr_t dc_flags)
{
    dispatch_tid tid = _dispatch_tid_self();

    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;
	
    if (unlikely(!_dispatch_queue_try_acquire_barrier_sync(dl, tid))) {
	return _dispatch_sync_f_slow(dl, ctxt, func, DC_FLAG_BARRIER, dl,
				DC_FLAG_BARRIER | dc_flags);
    }

    if (unlikely(dl->do_targetq->do_targetq)) {
	return _dispatch_sync_recurse(dl, ctxt, func,
				DC_FLAG_BARRIER | dc_flags);
    }
    _dispatch_introspection_sync_begin(dl);
    _dispatch_lane_barrier_sync_invoke_and_complete(dl, ctxt, func
			DISPATCH_TRACE_ARG(_dispatch_trace_item_sync_push_pop(
					dq, ctxt, func, dc_flags | DC_FLAG_BARRIER)));
}

这里的函数内部实现,和我们之前的同步函数内部实现可以说是整体思路是一样的。也包括一个 _dispatch_sync_f_slow (内部可能发生死锁问题)。 这里的方法调用中 会 多出一个标识 DC_FLAG_BARRIER 来标识栅栏函数。这里出现了分支, 下面我们通过下符号断点来调试栅栏函数执行流程: 函数执行来到

_dispatch_sync_f_slow

image.png

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)
{
	dispatch_queue_t top_dq = top_dqu._dq;
	dispatch_queue_t dq = dqu._dq;
	if (unlikely(!dq->do_targetq)) {
		return _dispatch_sync_function_invoke(dq, ctxt, func);
	}

	pthread_priority_t pp = _dispatch_get_priority();
	struct dispatch_sync_context_s dsc = {
		.dc_flags    = DC_FLAG_SYNC_WAITER | dc_flags,
		.dc_func     = _dispatch_async_and_wait_invoke,
		.dc_ctxt     = &dsc,
		.dc_other    = top_dq,
		.dc_priority = pp | _PTHREAD_PRIORITY_ENFORCE_FLAG,
		.dc_voucher  = _voucher_get(),
		.dsc_func    = func,
		.dsc_ctxt    = ctxt,
		.dsc_waiter  = _dispatch_tid_self(),
	};

	_dispatch_trace_item_push(top_dq, &dsc);
	__DISPATCH_WAIT_FOR_QUEUE__(&dsc, dq);

	if (dsc.dsc_func == NULL) {
		// dsc_func being cleared means that the block ran on another thread ie.
		// case (2) as listed in _dispatch_async_and_wait_f_slow.
		dispatch_queue_t stop_dq = dsc.dc_other;
		return _dispatch_sync_complete_recurse(top_dq, stop_dq, top_dc_flags);
	}

	_dispatch_introspection_sync_begin(top_dq);
	_dispatch_trace_item_pop(top_dq, &dsc);
	_dispatch_sync_invoke_and_complete_recurse(top_dq, ctxt, func,top_dc_flags
			DISPATCH_TRACE_ARG(&dsc));
}

接着是 来到了 _dispatch_sync_complete_recurse:

_dispatch_sync_complete_recurse
static void
_dispatch_sync_complete_recurse(dispatch_queue_t dq, dispatch_queue_t stop_dq,
		uintptr_t dc_flags)
{
    bool barrier = (dc_flags & DC_FLAG_BARRIER);
    do {
	if (dq == stop_dq) return;
        if (barrier) {
            //有栅栏的时候,会先将栅栏函数之前的任务调度执行完成
            dx_wakeup(dq, 0, DISPATCH_WAKEUP_BARRIER_COMPLETE);
	} else {
            //全部任务执行完毕后,没有栅栏函数了
            //告诉系统已经执行完成,回对状态进行相关的处理
            _dispatch_lane_non_barrier_complete(upcast(dq)._dl, 0);
	}
	dq = dq->do_targetq;
	barrier = (dq->dq_width == 1);
    } while (unlikely(dq->do_targetq));
}


...

#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)

...

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_serial, lane,
	.do_type        = DISPATCH_QUEUE_SERIAL_TYPE,
	.do_dispose     = _dispatch_lane_dispose,
	.do_debug       = _dispatch_queue_debug,
	.do_invoke      = _dispatch_lane_invoke,

	.dq_activate    = _dispatch_lane_activate,
	.dq_wakeup      = _dispatch_lane_wakeup,
	.dq_push        = _dispatch_lane_push,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_concurrent, lane,
	.do_type        = DISPATCH_QUEUE_CONCURRENT_TYPE,
	.do_dispose     = _dispatch_lane_dispose,
	.do_debug       = _dispatch_queue_debug,
	.do_invoke      = _dispatch_lane_invoke,

	.dq_activate    = _dispatch_lane_activate,
	.dq_wakeup      = _dispatch_lane_wakeup,
	.dq_push        = _dispatch_lane_concurrent_push,
);

DISPATCH_VTABLE_SUBCLASS_INSTANCE(queue_global, lane,
	.do_type        = DISPATCH_QUEUE_GLOBAL_ROOT_TYPE,
	.do_dispose     = _dispatch_object_no_dispose,
	.do_debug       = _dispatch_queue_debug,
	.do_invoke      = _dispatch_object_no_invoke,

	.dq_activate    = _dispatch_queue_no_activate,
	.dq_wakeup      = _dispatch_root_queue_wakeup,
	.dq_push        = _dispatch_root_queue_push,
);

可以看到这里执行的是一个 do{ }while() 循环, 如果有 barrier 存在的话,会 dx_wakeup 操作(不同的队列具体执行内容不太一致),我们先看自定义并发队列的执行内容:

自定义并发队列

_dispatch_lane_wakeup
void
_dispatch_lane_wakeup(dispatch_lane_class_t dqu, dispatch_qos_t qos,
		dispatch_wakeup_flags_t flags)
{
	dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;

	if (unlikely(flags & DISPATCH_WAKEUP_BARRIER_COMPLETE)) {
            // 栅栏函数 调用
            return _dispatch_lane_barrier_complete(dqu, qos, flags);
	}
	if (_dispatch_queue_class_probe(dqu)) {
            target = DISPATCH_QUEUE_WAKEUP_TARGET;
	}
        //  同步函数和异步函数
	return _dispatch_queue_wakeup(dqu, qos, flags, target);
}

这里,会将在栅栏函数之前添加的任务全部循环调用执行。任务在执行完毕后,再调用栅栏函数,最后,会来到 _dispatch_lane_barrier_complete 的执行。

_dispatch_lane_barrier_complete
static void
_dispatch_lane_barrier_complete(dispatch_lane_class_t dqu, dispatch_qos_t qos,
		dispatch_wakeup_flags_t flags)
{
    dispatch_queue_wakeup_target_t target = DISPATCH_QUEUE_WAKEUP_NONE;
dispatch_lane_t dq = dqu._dl;

    if (dq->dq_items_tail && !DISPATCH_QUEUE_IS_SUSPENDED(dq)) {
		struct dispatch_object_s *dc = _dispatch_queue_get_head(dq);
	if (likely(dq->dq_width == 1 || _dispatch_object_is_barrier(dc))) {
            //串行队列
            if (_dispatch_object_is_waiter(dc)) {
                //等待
                return _dispatch_lane_drain_barrier_waiter(dq, dc, flags, 0);
            }
        } else if (dq->dq_width > 1 && !_dispatch_object_is_barrier(dc)) {
            //并发队列, 执行栅栏函数任务 - 拔掉栅栏
            return _dispatch_lane_drain_non_barriers(dq, dc, flags);
        }

        if (!(flags & DISPATCH_WAKEUP_CONSUME_2)) {	
            _dispatch_retain_2(dq);
            flags |= DISPATCH_WAKEUP_CONSUME_2;
        }
        target = DISPATCH_QUEUE_WAKEUP_TARGET;
    }

    uint64_t owned = DISPATCH_QUEUE_IN_BARRIER +
                            dq->dq_width * DISPATCH_QUEUE_WIDTH_INTERVAL;  
    //栅栏都执行完
    //将栅栏标签清掉 - 以执行后面的函数任务
    return _dispatch_lane_class_barrier_complete(dq, qos, flags, target, owned);
}

全局并发队列

_dispatch_root_queue_wakeup
void
_dispatch_root_queue_wakeup(dispatch_queue_global_t dq,
		DISPATCH_UNUSED dispatch_qos_t qos, dispatch_wakeup_flags_t flags)
{
	if (!(flags & DISPATCH_WAKEUP_BLOCK_WAIT)) {
		DISPATCH_INTERNAL_CRASH(dq->dq_priority,
				"Don't try to wake up or override a root queue");
	}
	if (flags & DISPATCH_WAKEUP_CONSUME_2) {
		return _dispatch_release_2_tailcall(dq);
	}
}

方法中并没有对栅栏函数的判断处理,所以也就是全局并发队列栅栏函数并没有起到作用的原因。

栅栏函数的问题

栅栏函数只能阻塞同一个队列中的任务执行。这是栅栏函数在平时使用中比较少的原因。因为实际开发中由于业务的复杂、封装。栅栏就无法获取队列了,所以,因为这个局限性,实际使用的较少。