准备
一、栅栏函数的应用
作用
- 控制任务执行顺序,同步。
函数
dispatch_barrier_async
:前面的任务执行完毕才会来到这里,这里执行完毕才会执行后边的任务。dispatch_barrier_sync
:作用相同,但是这个会堵塞线程,影响后面的任务执行。- 非常重要的一点:栅栏函数只能控制同一并发队列。
dispatch_barrier_async 示例
- (void)myDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("ssl", DISPATCH_QUEUE_CONCURRENT);
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务1");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务2");
});
// 栅栏函数
dispatch_barrier_async(concurrentQueue, ^{
sleep(1);
NSLog(@"------barrier------");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
NSLog(@"任务3");
});
NSLog(@"--------干干干--------");
}
执行结果:
--------干干干--------
任务1
任务2
-------barrier-------
任务3
任务1
、任务2
、barrier
中都是有延迟,但执行结果是先执行了任务1
和任务2
,然后执行barrier
,最后执行下面的任务3
,确实如上面所说前面的任务执行完才会执行barrier
和后面的任务。
dispatch_barrier_sync 示例
- (void)myDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("ssl", DISPATCH_QUEUE_CONCURRENT);
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务1");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务2");
});
// 栅栏函数
dispatch_barrier_sync(concurrentQueue, ^{
sleep(1);
NSLog(@"------barrier------");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
NSLog(@"任务3");
});
NSLog(@"--------干干干--------");
}
执行结果:
任务2
任务1
------barrier------
--------干干干--------
任务3
- 可以看到
sync
函数不仅有async
函数的作用,还有堵塞线程的作用,主线程中的干干干
也是在barrier
后面才执行。
dispatch_get_global_queue 示例
将普通队列换成全局并发队列
,查看执行结果:
- (void)myDemo {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务1");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"任务2");
});
// 栅栏函数
dispatch_barrier_async(concurrentQueue, ^{
sleep(1);
NSLog(@"------barrier------");
});
// 异步函数
dispatch_async(concurrentQueue, ^{
NSLog(@"任务3");
});
NSLog(@"--------干干干--------");
}
执行结果:
--------干干干--------
任务3
------barrier------
任务2
任务1
- 可以看到在全局并发队列中,栅栏函数的作用失效了。
流程控制案例
看下面这个案例:
- (void)myDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("ssl", DISPATCH_QUEUE_CONCURRENT);
// 多线程 操作marray
for (int i = 0; i < 1000; i++) {
dispatch_async(concurrentQueue, ^{
NSString *imageName = [NSString stringWithFormat:@"%d.jpg", (i % 10)];
NSURL *url = [[NSBundle mainBundle] URLForResource:imageName withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"数组的个数:%zd",self.mArray.count);
}
执行结果:
数组的个数:996
- 数组的个数不一定是固定的
996
,但是是小于1000
的。 - 因为这样操作线程是不安全的,当两个任务同时进行
addObject
操作时,假设这时mArray
的数量是800
,那么两个任务都会向801
的位置进行赋值操作,其中的一个就会被覆盖,从而导致最终数组的个数小于1000
。
将[self.mArray addObject:image]
放到栅栏函数中,来保证线程的安全:
- (void)myDemo {
...
dispatch_barrier_async(concurrentQueue , ^{
[self.mArray addObject:image];
});
...
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"数组的个数:%zd",self.mArray.count);
}
执行结果:
数组的个数:1000
- 栅栏函数会保证前面的任务执行完毕,再执行后面的任务,任务会一个一个的执行,保证了线程的安全,所以最终数组的个数是
1000
。
二、栅栏函数的底层原理
我们以同步的栅栏函数进行底层原理的分析。
栅栏函数之前的任务执行
进入dispatch_barrier_async
:
进入_dispatch_barrier_sync_f
-> _dispatch_barrier_sync_f_inline
->
_dispatch_sync_f_slow
:
符号断点跟流程:
- 可以看到
栅栏
之前的任务是在__DISPATCH_WAIT_FOR_QUEUE__
中执行的,这个函数在 上一篇 也提到过,死锁
就是在这个函数中发生的。
栅栏函数之后的任务执行
接下来进入_dispatch_sync_invoke_and_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) {
// 执行栅栏函数
// #define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
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));
}
断点跟流程验证:
接下来会继续调用到_dispatch_sync_f_slow
函数,完成所有任务的执行:
总结
- 首先通过
__DISPATCH_WAIT_FOR_QUEUE__
函数将先加入队列的任务执行完。 - 然后
do while
循环,判断如果有barrier
,先执行barrier
任务,最后再循环执行剩下的任务。
三、信号量的使用
作用
- 同步->当锁,控制
GCD
最大并发数。
相关函数
dispatch_semaphore_create
:创建信号量。dispatch_semaphore_wait
:信号量等待。dispatch_semaphore_signal
:信号量释放。
示例
- (void)myDemo {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
sleep(2);
NSLog(@"执行任务1");
NSLog(@"任务1完成");
dispatch_semaphore_signal(sem); // 发信号
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
sleep(1);
NSLog(@"执行任务2");
NSLog(@"任务2完成");
dispatch_semaphore_signal(sem); // 发信号
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 等待
NSLog(@"执行任务3");
NSLog(@"任务3完成");
dispatch_semaphore_signal(sem);
});
NSLog(@"任务4");
}
执行结果:
任务4
执行任务1
任务1完成
执行任务2
任务2完成
执行任务3
任务3完成
任务1
和任务2
都有延迟操作,它们本应该后执行的,因为加了信号量,任务却是从上到下执行的,任务1
和任务2
反而先执行了。
四、信号量的底层原理
dispatch_semaphore_create
- 可以看到信号量的创建主要就是给
dsema_value
赋值,赋值为我们传进来的值,接下来继续分析。
dispatch_semaphore_wait
intptr_t
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
{
long value = os_atomic_dec2o(dsema, dsema_value, acquire);
if (likely(value >= 0)) {
return 0;
}
return _dispatch_semaphore_wait_slow(dsema, timeout);
}
- 对
dsema_value
进行--
操作,如果>= 0
,函数返回0
,任务可以执行。 - 否则,返回
_dispatch_semaphore_signal_slow
函数。
进入_dispatch_semaphore_wait_slow
:
- 当
timeout
是DISPATCH_TIME_FOREVER
,将会进行等待。
进入_dispatch_sema4_wait
函数:
- 可以看到这里是一个
do while
循环,所以dispatch_semaphore_wait
下面的代码不能执行。 - 当
dsema_value >= 0
时do while
循环结束,任务又可以正常执行,这就涉及到了dsema_value
的++
操作,下面来看dispatch_semaphore_signal
函数。
dispatch_semaphore_signal
dispatch_semaphore_signal(dispatch_semaphore_t dsema)
{
long value = os_atomic_inc2o(dsema, dsema_value, release);
if (likely(value > 0)) {
return 0;
}
if (unlikely(value == LONG_MIN)) {
DISPATCH_CLIENT_CRASH(value,
"Unbalanced call to dispatch_semaphore_signal()");
}
return _dispatch_semaphore_signal_slow(dsema);
}
- 对
dsema_value
进行++
操作,如果> 0
,函数返回0
。 - 如果
value == LONG_MIN
,程序崩溃。 - 上面的条件都不成立,返回
_dispatch_semaphore_signal_slow
函数。
进入_dispatch_semaphore_signal_slow
函数,可以看到是一些异常的处理:
总结
- 信号量操作主要就是
dsema_value
的相关处理。 dispatch_semaphore_create
初始化dsema_value
值。dispatch_semaphore_wait
进行dsema_value
的--
操作,如果>= 0
任务执行,否则进行do while
循环阻塞任务执行,等待dsema_value
值>= 0
任务可以继续执行。dispatch_semaphore_signal
进行dsema_value
的++
操作。
五、调度组的应用
作用
- 控制任务执行顺序。
函数
dispatch_group_create
:创建组。dispatch_group_async
:进组任务。dispatch_group_notify
:进组任务执行完毕通知。dispatch_group_wait
:进组任务执行等待时间。dispatch_group_enter
:进组。dispatch_group_leave
:出组。
示例
- (void)myGroupDemo
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//创建调度组
dispatch_group_async(group, queue, ^{
sleep(1);
[self.mArray addObject:@"图片1"];
});
// 进组和出租 成对 先进后出
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(1);
[self.mArray addObject:@"图片2"];
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSMutableString *muStr = [NSMutableString new];
for (NSString *str in self.mArray) {
[muStr appendString:str];
}
[muStr appendString:@"-生成水印"];
NSLog(@"%@",muStr);
});
}
执行结果:
图片1图片2-生成水印
- 当两个异步任务都完成以后,才会调用到
group
任务,下面来分析一下底层原理。
六、调度组的原理
调度组早先版本是用信号量来实现的,现在自己写了一套,也是仿照信号量的原理,都是关于value
的一些操作。
dispatch_group_create
dispatch_group_t
dispatch_group_create(void)
{
return _dispatch_group_create_with_count(0);
}
进入_dispatch_group_create_with_count
:
static inline dispatch_group_t
_dispatch_group_create_with_count(uint32_t n)
{
dispatch_group_t dg = _dispatch_object_alloc(DISPATCH_VTABLE(group),
sizeof(struct dispatch_group_s));
dg->do_next = DISPATCH_OBJECT_LISTLESS;
dg->do_targetq = _dispatch_get_default_queue(false);
if (n) {
os_atomic_store2o(dg, dg_bits,
(uint32_t)-n * DISPATCH_GROUP_VALUE_INTERVAL, relaxed);
os_atomic_store2o(dg, do_ref_cnt, 1, relaxed); // <rdar://22318411>
}
return dg;
}
- 这里是进行了一些初始化的操作。
dispatch_group_enter
void dispatch_group_enter(dispatch_group_t dg)
{
// The value is decremented on a 32bits wide atomic so that the carry
// for the 0 -> -1 transition is not propagated to the upper 32bits.
uint32_t old_bits = os_atomic_sub_orig2o(dg, dg_bits,
DISPATCH_GROUP_VALUE_INTERVAL, acquire);
uint32_t old_value = old_bits & DISPATCH_GROUP_VALUE_MASK;
if (unlikely(old_value == 0)) {
_dispatch_retain(dg); // <rdar://problem/22318411>
}
if (unlikely(old_value == DISPATCH_GROUP_VALUE_MAX)) {
DISPATCH_CLIENT_CRASH(old_bits,
"Too many nested calls to dispatch_group_enter()");
}
}
- 对
value
进行--
操作。 - 如果
old_value
等于0
,调用_dispatch_retain
函数 - 如果
old_value
等于DISPATCH_GROUP_VALUE_MAX
,程序崩溃,DISPATCH_GROUP_VALUE_MAX
的值为4
:#define DISPATCH_GROUP_VALUE_INTERVAL 0x0000000000000004ULL #define DISPATCH_GROUP_VALUE_MAX DISPATCH_GROUP_VALUE_INTERVAL
dispatch_group_leave
- 这里会执行
value
的++
操作,如果old_value
等于-1
,调用_dispatch_group_wake
执行唤醒操作,唤醒操作会调用dispatch_group_notify
中的block
任务。
dispatch_group_notify
- 当
old_state == 0
也就是value == 0
时,执行_dispatch_group_wake
,调用dispatch_group_notify
中的block
任务。
dispatch_group_async
我们猜测dispatch_group_async
函数中应该是有这dispatch_group_enter
和dispatch_group_leave
的调用,接下来进行探索。
进入dispatch_group_async
:
进入_dispatch_continuation_group_async
:
- 在这里找到了
dispatch_group_enter
函数的调用,继续看_dispatch_continuation_async
。
我们在 iOS 多线程(二):GCD基础&源码分析上 中分析过,通过_dispatch_continuation_async
最终会调用到_dispatch_continuation_invoke_inline
函数:
- 我们之前分析的正常情况会调用
_dispatch_client_callout
函数,但如果标记是DC_FLAG_GROUP_ASYNC
的时候,调用的是_dispatch_continuation_with_group_invoke
函数。
进入_dispatch_continuation_with_group_invoke
:
- 如上图,当
type == DISPATCH_GROUP_TYPE
时,先通过_dispatch_client_callout
完成block
任务的调用,然后就会调用dispatch_group_leave
函数,完美!!。
总结
dispatch_group_create
:初始化value = 0
;dispatch_group_enter
:value--
;dispatch_group_leave
:value++
,如果old_value == -1
,调用dispatch_group_notify
中的block
任务。dispatch_group_notify
:如果value == 0
,调用dispatch_group_notify
中的block
任务。dispatch_group_async
:内部会调用成对的dispatch_group_enter
和dispatch_group_leave
。
七、dispatch_source
作用
- 其
CPU
负荷非常小,尽量不占用资源。 - 联结的优势。
概念
在任一线程上调用它的一个函数dispatch_source_merge_data
后,会执行Dispatch Source
实现定义好的句柄(可以把句柄简单理解为一个block
)这个过程叫Custom event
,用户事件,是dispatch source
支持处理的一种事件。
句柄是一种指向指针的指针,它指向的就是一个类或者结构,它和系统有很密切的关系
HINSTANCE
(实例句柄),HBITMAP
(位图句柄),HDC
(设备表述句柄),HICON
(图标句柄)等。这当中还有一个通用的句柄,就是HANDLE
。
函数
dispatch_source_create
:创建源。dispatch_source_set_event_handler
:设置源事件回调。dispatch_source_merge_data
:源事件设置数据。dispatch_source_get_data
:获取源事件数据。dispatch_resume
:继续。dispatch_suspend
:挂起。
基本使用
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic) BOOL isRunning;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalComplete = 0;
self.queue = dispatch_queue_create("com.ssl.dd", 0);
第一个参数:dispatch_source_type_t type为设置GCD源方法的类型,前面已经列举过了。
第二个参数:uintptr_t handle Apple的API介绍说,暂时没有使用,传0即可。
第三个参数:unsigned long mask Apple的API介绍说,使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。
第四个参数:dispatch_queue_t _Nullable queue 队列,将定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,时间处理的回调内,需要异步获取UI线程,更新UI...不过这好像是常识,又啰嗦了...
*/
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// 保存代码块 ---> 异步 dispatch_source_set_event_handler()
// 设置取消回调 dispatch_source_set_cancel_handler(dispatch_source_t source,dispatch_block_t _Nullable handler)
// 封装我们需要回调的触发函数 -- 响应
dispatch_source_set_event_handler(self.source, ^{
NSUInteger value = dispatch_source_get_data(self.source); // 取回来值 1 响应式
self.totalComplete += value;
NSLog(@"进度:%.2f", self.totalComplete/100.0);
self.progressView.progress = self.totalComplete/100.0;
});
self.isRunning = YES;
dispatch_resume(self.source);
}
- (IBAction)didClickStartOrPauseAction:(id)sender {
if (self.isRunning) {// 正在跑就暂停
dispatch_suspend(self.source);
dispatch_suspend(self.queue);// mainqueue 挂起
self.isRunning = NO;
[sender setTitle:@"暂停中..." forState:UIControlStateNormal];
}else{
dispatch_resume(self.source);
dispatch_resume(self.queue);
self.isRunning = YES;
[sender setTitle:@"加载中..." forState:UIControlStateNormal];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"点击开始加载");
for (NSUInteger index = 0; index < 100; index++) {
dispatch_async(self.queue, ^{
if (!self.isRunning) {
NSLog(@"暂停下载");
return ;
}
sleep(2);
dispatch_source_merge_data(self.source, 1); // source 值响应
});
}
}
打印结果: