持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
今天我们继续探索GCD,单例的原理,什么是栅栏函数,什么是信号量,dispatch_source的运用等。
一. GCD线程队列探索下
1、单例的原理
现在就来看看平时我们写的单例代码:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
//进入源码
#ifdef __BLOCKS__
void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
#endif
/////////////////////////////////////////////////////////////////////////
void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
//dispatch_once_t强转dispatch_once_gate_t
dispatch_once_gate_t l = (dispatch_once_gate_t)val;
//判断func 是否被执行,被执行直接return
#if !DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER
uintptr_t v = os_atomic_load(&l->dgo_once, acquire);
if (likely(v == DLOCK_ONCE_DONE)) {
return;
}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
if (likely(DISPATCH_ONCE_IS_GEN(v))) {
return _dispatch_once_mark_done_if_quiesced(l, v);
}
#endif
#endif
if (_dispatch_once_gate_tryenter(l)) {
return _dispatch_once_callout(l, ctxt, func);
}
return _dispatch_once_wait(l);
}
复制代码
只有&l->dgo_once
的状态等于DLOCK_ONCE_UNLOCKED
的时候才会进行_dispatch_once_callout
的操作。 _dispatch_once_callout->_dispatch_once_gate_broadcast->_dispatch_once_mark_done
static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
//&dgo->dgo_once把状态赋值成DLOCK_ONCE_DONE
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
复制代码
把&dgo->dgo_once = DLOCK_ONCE_DONE;//标记
2、栅栏函数
dispatch_barrier_async栅栏函数的效果
:等待栅栏函数前添加到队列⾥⾯的任务全部执⾏完成之后,才会执⾏栅栏函数⾥⾯的任务,栅栏函数⾥⾯的任务执⾏完成之后才会执⾏栅栏函数后⾯的队列⾥⾯的任务。 需要注意的点:
-
栅栏函数只对
同⼀队列起作⽤
。 -
栅栏函数对
全局并发队列⽆效
。
通过例子来充分理解:
dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_async(t, ^{
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
复制代码
创建了一个并发队列t,添加了异步打印1,2,添加栅栏函数异步打印3,添加异步打印5,会先打印4,1,2打印412顺序位置不确定,完打印3,然后打印5 打印 41235,12435,14235等 打印结果和我们分析的一样。
继续看例子二:
dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t t2 = dispatch_get_global_queue(0, 0);
dispatch_async(t2, ^{
NSLog(@"1");
});
dispatch_async(t2, ^{
sleep(3);
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
sleep(4);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t2, ^{
NSLog(@"5");
});
复制代码
创建t2全局并发队列,然后添加异步打印1,2 (sleep 3秒),然后添加栅栏函数打印3 (sleep 4秒),打印4,打印5. 打印结果: 发现栅栏函数
并没有拦住12的打印,让5打印在3之后。验证了,我们之前的结论,栅栏函数
无法拦住全局并发队列。为什么拦截不了呢?
继续深入探索。
进入源码libdispatch-1271:搜索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);
}
void
dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
dispatch_function_t func)
{
_dispatch_barrier_sync_f_inline(dq, ctxt, func, 0);
}
复制代码
我们通过符号断点,来跟踪一下这个几个函数的运行。 发现运行到了_dispatch_sync_f_slow
的符号断点中. 通过_dispatch_sync_f_slow->_dispatch_sync_function_invoke->_dispatch_sync_function_invoke_inline->_dispatch_client_callout:
我们继续符号断点,进行反正法推导验证:
dispatch_queue_t t = dispatch_queue_create("ny", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t t2 = dispatch_get_global_queue(0, 0);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
sleep(30);
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_sync(t, ^{
sleep(5);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
复制代码
继续执行。 在同步栅栏函数执行时,会等待
异步队列t中的 1,2打印执行完成才执行。同时栅栏函数执行完才能执行队列后面的函数。
然后我们使用,全局并发队列再次运行。 在全局并发队列中,栅栏函数执行并没等待队列任务2执行完成
。就马上执行了打印3.
3、group调度组
调度组的效果
:等待调度组前⾯的任务执⾏完才会执⾏dispatch_group_notify
函数⾥⾯的任务。 调度组和队列没有关系,只要是同⼀调度组就可以。
分析例子:
dispatch_group_t g = dispatch_group_create();
dispatch_queue_t que1 = dispatch_queue_create("l1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t que2 = dispatch_queue_create("l2", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(g);
dispatch_async(que1, ^{
sleep(2);
NSLog(@"1");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(que2, ^{
sleep(3);
NSLog(@"2");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(dispatch_get_global_queue(0, 0 ), ^{
sleep(4);
NSLog(@"3");
dispatch_group_leave(g);
});
dispatch_group_enter(g);
dispatch_async(dispatch_get_main_queue(), ^{
sleep(5);
NSLog(@"4");
dispatch_group_leave(g);
});
dispatch_group_notify(g, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
复制代码
打印就是 1234 执行完了才会执行打印5. 然后1234是有序的。
dispatch_group_enter
和 dispatch_group_leave
类似信号量的+,- 。然后等于0时执行dispatch_group_notify
4、什么是信号量
信号量dispatch_semaphore
dispatch_semaphore
主要就是三个⽅法:
-
dispatch_semaphore_create(long value);
这个函数是创建⼀个dispatch_semaphore_t
类型的信号量,并且创建的时候需要指定信号量的⼤⼩。 -
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
等待信号量。如果信号量值为0,那么该函数就会⼀直等待,也就是不返回(相当于阻塞当前线程),直到该函数等待的信号量的值⼤于等于1,该函数会对信号量的值进⾏减1操作,然后返回。
3.dispatch_semaphore_signal(dispatch_semaphore_t deem);
发送信号量。该函数会对信号量 的值进⾏加1操作。
通过这三个⽅法,就能控制GCD的最⼤并发数量。信号量在使⽤的时候需要注意: dispatch_semaphore_wait
和 dispatch_semaphore_signal
⼀定 要成对出现。因为在信号量释放的时候,如果dsema_orig
初始信号量的⼤⼩⼤于dsema_value
(通 过dispatch_semaphore_wait和dispatch_semaphore_signal
改变之后的信号量的⼤⼩)就会触发崩 溃。 通过一个例子进行分析:
dispatch_semaphore_t sem = dispatch_semaphore_create(0); //创建初始信号量
dispatch_async(dispatch_get_global_queue(0, 0), ^{ //加入全局并发队列
NSLog(@"1");
dispatch_semaphore_signal(sem); //信号+1 1
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//如果信号量值为0,那么该函数就会⼀直等待
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_semaphore_signal(sem);//信号+1
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
dispatch_semaphore_signal(sem);//信号+1
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"4");
dispatch_semaphore_signal(sem);//信号+1
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
dispatch_semaphore_signal(sem);//信号+1
});
复制代码
分析:sem=0 执行添加全局队列任务并 执行打印1,然后等待->任务1执行完成并对sem+1,然后dispatch_semaphore_wait
不在等待进行任务2 并且对sem-1,在任务2 执行完时dispatch_semaphore_signal
对sem+1 ,如此反复运行到任务5.所以打印有顺序 12345. 我们进入源码一探究竟: 继续探索dispatch_semaphore_wait
_dispatch_semaphore_wait_slow->_dispatch_sema4_wait
5、dispatch_source
dispatch_source
是⽤来监听事件的,可以创建不同类型的dispatch_source
来监听不同的事件。
dispatch_source
可以监听的事件类型:
dispatch_source
的具体⽤法:在任⼀线程上调⽤它的dispatch_source_merge_data
函数,会执⾏
dispatch_source
事先定义好的句柄(可以把句柄简单理解为⼀个block)。
dispatch_source
的⼏个⽅法:
定时器监听小例子: