libdispatch-1271.120.2 下载
苹果官方资源opensource
多线程相关文献:
iOS 多线程原理 - 线程与队列底层
iOS 多线程原理 - GCD函数底层
iOS 线程底层 - 锁
本章节探究:
1.单例 dispatch_once
2.栅栏函数 barrier
3.调度组 group
4.信号量 semaphore
5.dispatch_source
前言
在了解了线程与队列的底层原理之后,本章节来看看GCD函数的底层原理,研究这些API是怎么调用的,并附上使用案例。
一、单例
+ (SingleExample *)shareInstance {
static SingleExample *single = nil;
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
single = [[SingleExample alloc] init];
}) ;
return single;
}
来看看dispatch_once
这个函数原理。
打开libdispatch源码
dispatch_once
的源码声明:
dispatch_once_t *val
它里面有一个状态的记录,来保证block
只被调用一次。
dispatch_once_f
的源码声明:
需要看看执行func
的要出于什么条件下才会被执行 (_dispatch_once_gate_tryenter
)
_dispatch_once_gate_tryenter
的源码声明:
而等待是怎么等的呢?(_dispatch_once_wait
)
_dispatch_once_wait
的源码声明:
在死循环里不断地查询单例状态,一旦任务执行完毕才跳出循环。
单例总结:
1.线程安全
2.任务只会被执行一次
3.通过一个状态值来保证任务是否被执行过
二、栅栏函数
同步栅栏函数dispatch_barrier_sync
案例:
- (void)test_barrier {
dispatch_queue_t t = dispatch_queue_create("AnAn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_sync(t, ^{
sleep(2);
NSLog(@"%@", [NSThread currentThread]); // main
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 12顺序不一定;3一定在12后面;45在3后面;45顺序不一定
// 同步栅栏dispatch_barrier_sync 和 普通的同步dispatch_sync效果是一样的
异步栅栏函数dispatch_barrier_async
案例:
- (void)test_barrier {
dispatch_queue_t t = dispatch_queue_create("AnAn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 异步栅栏函数
dispatch_barrier_async(t, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 124顺序不一定,3一定在12后面,5一定在3后面
// 异步栅栏函数只能栅得住非全局队列的任务
全局队列案例
:
- (void)test_barrier_global {
dispatch_queue_t t = dispatch_get_global_queue(0, 0);
dispatch_async(t, ^{
NSLog(@"1");
});
dispatch_async(t, ^{
NSLog(@"2");
});
// 栅栏函数
dispatch_barrier_async(t, ^{
sleep(2);
NSLog(@"3");
});
NSLog(@"4");
dispatch_async(t, ^{
NSLog(@"5");
});
}
// 1245没有顺序 3在最后
// 异步栅栏函数栅不住全局队列里的任务
栅栏函数分为同步栅栏
和异步栅栏
。
dispatch_barrier_async
在自定义的并发队列
里,全局和串行
达不到我们要的效果。
苹果文档中指出,如果使用的是全局队列或者创建的不是并发队列,则dispatch_barrier_async
实际上就相当于dispatch_async
。
1.同步栅栏dispatch_barrier_sync
其实同步栅栏与普通同步实现的效果是差不多的,在源码上只有一点点小差异。
dispatch_barrier_sync _dispatch_barrier_sync_f _dispatch_barrier_sync_f_inline_dispatch_barrier_sync_f_inline
里会判断不同的队列条件来去选择分支继续往下走,这里我以并发队列为例
,它会走_dispatch_sync_f_slow
代码分支:
以并发队列为例
,它会走_dispatch_sync_invoke_and_complete_recurse
代码分支:
_dispatch_sync_function_invoke_inline
和dispatch_sync底层一样的,是去调用func
。
栅栏函数完成任务后会执行_dispatch_sync_complete_recurse
唤醒队列里后续的任务。
_dispatch_sync_complete_recurse
里通过do...while
去唤醒队列里的任务dx_wakeup
;
dx_wakeup
是一个dq_wakeup
的宏定义:
#define dx_wakeup(x, y, z) dx_vtable(x)->dq_wakeup(x, y, z)
dq_wakeup
(栅栏函数栅不住全局队列的原因就在这里,因为它指定的wakeup
函数不一样。)
唤醒以并发队列为例
,它会走_dispatch_lane_wakeup
为barrier形式,调用_dispatch_lane_barrier_complete
:
- 如果是
串行队列
,则会进行等待,等待其他的任务执行完成,再按顺序执行; - 如果是
并发队列
,则会调用_dispatch_lane_drain_non_barriers
方法将栅栏之前的任务执行完成; - 最后会调用
_dispatch_lane_class_barrier_complete
方法,也就是把栅栏拔掉了,不拦了,从而执行栅栏之后的任务。
唤醒以全局并发队列为例
,它会走_dispatch_root_queue_wakeup
它里面就没有拦截有关栅栏函数相关的东西。
同步栅栏函数dispatch_barrier_sync
和普通同步函数dispatch_sync
效果是一样的:
阻塞当前线程,不开辟线程,立即执行,同步栅栏函数还需要等栅栏任务完成后唤醒非全局队列后续的任务
为什么苹果设计栅栏函数栅不住全局并发队列?
因为我们系统也会使用全局并发队列,避免造成系统任务被阻塞。
2.异步栅栏dispatch_barrier_async
dispatch_barrier_async
-
_dispatch_continuation_init
保存任务
_dispatch_continuation_init
保存了任务,在需要执行的时候拿出来执行
_dispatch_continuation_async
dx_push
是宏定义:
#define dx_push(x, y, z) dx_vtable(x)->dq_push(x, y, z)
找到dq_push
的声明:
根据不同的队列赋值给dq_push
不一样的函数
以并发队列为例:
_dispatch_lane_concurrent_push
的源码声明:
_dispatch_lane_concurrent_push
就是栅栏异步与普通异步函数的分支:
走到dx_wakeup
函数,这里在同步栅栏部分已经介绍过了。
栅栏函数
总结:
1.栅栏函数只针对非全局队列;
2.栅栏函数不能栅住全局队列,因为系统也在用它,防止阻塞住系统任务;
3.栅栏函数需要等待当前队列前面的任务执行完,再去执行栅栏任务,最后唤醒执行栅栏任务后面的任务
三、调度组
- (void)test_group {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t que1 = dispatch_queue_create("An", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t que2 = dispatch_queue_create("Lin", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_enter(group);
dispatch_async(que1, ^{
sleep(4);
NSLog(@"1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(que2, ^{
sleep(3);
NSLog(@"2");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0 ), ^{
sleep(2);
NSLog(@"3");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_main_queue(), ^{
sleep(1);
NSLog(@"4");
dispatch_group_leave(group);
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"6");
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
});
}
// 5在4321任务之后
当然也可以使用dispatch_group_async
来代替dispatch_group_enter
和dispatch_group_leave
;效果是一样的。
研究的对象有三个:dispatch_group_enter
、dispatch_group_leave
、dispatch_group_notify
。
1. dispatch_group_enter
的源码分析
dispatch_group_enter
ps: 这里 DISPATCH_GROUP_VALUE_INTERVAL = 0x0000000000000004ULL
注释里说的 0->-1 跃迁的进位其实是位运算。实际上是+4
苹果官方文档对dispatch_group_enter
的解释:
调用此函数将增加组中当前未完成任务的计数。如果应用程序通过dispatch_group_async
函数以外的方式显式地从组中添加和删除任务,那么使用这个函数(与dispatch_group_leave
一起使用)允许您的应用程序正确地管理任务引用计数。对这个函数的调用必须与对dispatch_group_leave
的调用相平衡。您可以使用此函数同时将一个块与多个组关联。
2. dispatch_group_leave
的源码分析
dispatch_group_leave
苹果官方文档对dispatch_group_leave
的解释:
调用此函数将减少组中当前未完成任务的计数。如果应用程序通过dispatch_group_async
函数以外的方式显式地从组中添加和删除任务,那么使用这个函数(与dispatch_group_enter
一起使用)允许您的应用程序正确地管理任务引用计数。
对该函数的调用必须平衡对dispatch_group_enter
的调用。调用它的次数超过dispatch_group_enter是无效的
,这会导致负的计数。
3.dispatch_group_notify
的源码分析
dispatch_group_notify
dispatch_group_notify
dispatch_group_enter
通过改变调度组状态值+4;dispatch_group_leave
通过调度组状态值-4;dispatch_group_notify
在调度组内不断地获取调度组状态值,如果状态值达到平衡(等于0),则说明前面的任务做完了,需要执行notify里的任务。
调度组总结:
1.dispatch_group_enter
与dispatch_group_leave
必须成对使用;
2.dispatch_group_leave
次数多于dispatch_group_enter
会导致崩溃;
3.调度组底层是通过修改调度组的状态值的增(enter
)减(leave
),不断地监听这个状态值是否达到平衡(等于0),一旦平衡则去执行dispatch_group_notify
里的任务。
四、信号量
- (void)test_semaphore {
// 设置0,任务1不需要等;设置1,任务1和2不需要等...
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
dispatch_semaphore_signal(sem);
});
// 等待任务1的signal
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
NSLog(@"2");
dispatch_semaphore_signal(sem);
});
// 等待任务2的signal
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"4");
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"5");
dispatch_semaphore_signal(sem);
});
}
// 12345
-
dispatch_semaphore_create
创建信号量,指定信号量数值 -
dispatch_semaphore_signal
发送信号量,将信号量数值+1 -
dispatch_semaphore_wait
等待信号量;当信号量数值为0时,阻塞当前线程一直等待;当信号量数值大于等于1时,将信号量数值-1并执行当前线程的任务
1.dispatch_semaphore_create
的源码声明:
dispatch_semaphore_signal声明
dispatch_semaphore_signal实现
- 当两个线程需要协调特定事件的完成时,为该值传递0很有用;
- 传递大于0的值对于管理有限的资源池很有用,其中池大小等于该值;
- 信号量的起始值传递小于信号量的起始值。
传递小于零的值将导致返回 NULL,也就是小于0就不会正常执行
。
总的来说:信号量初始值可以控制线程池中的最多并发数量
2.dispatch_semaphore_signal
的源码声明:
dispatch_semaphore_signal
os_atomic_inc2o
原子操作自增加1,然后会判断,如果value > 0
,就会返回0;
加一次后依然小于0就报异常 Unbalanced call to dispatch_semaphore_signal()
,然后会调用_dispatch_semaphore_signal_slow
做容错的处理。
3. dispatch_semaphore_wait
的源码声明:
dispatch_semaphore_wait
-
os_atomic_dec2o
进行原子自减1操作,也就是对value
值进行减操作,控制可并发数。 - 如果可并发数为2,则调用该方法后,变为1,表示现在并发数为 1,剩下还可同时执行1个任务,不会执行
_dispatch_semaphore_wait_slow
去等待。 - 如果初始值是0,减操作之后为负数,则会调用
_dispatch_semaphore_wait_slow
方法。
看看_dispatch_semaphore_wait_slow
的等待逻辑
一个do-while
循环,当不满足条件时,会一直循环下去,从而导致流程的阻塞。
上面举例里面就相当于,下图中的情况:
信号量总结:
1.dispatch_semaphore_wait
信号量等待,内部是对并发数做自减1操作,如果小于0
,会执行_dispatch_semaphore_wait_slow
然后调用_dispatch_sema4_wait
是一个do-while
,直到满足条件结束循环。
2.dispatch_semaphore_signal
信号量释放 ,内部是对并发数做自加1操作,直到大于0时,为可操作。
3.保持线程同步,将异步执行任务转换为同步执行任务。
4.保证线程安全,为线程加锁,相当于自旋锁。
五、dispatch_source
-
dispatch_source_create
创建源 -
dispatch_source_set_event_handler
设置源事件回调 -
dispatch_source_merge_data
源事件设置数据 -
dispatch_source_get_data
获取源事件数据 -
dispatch_resume
继续 -
dispatch_suspend
挂起 -
dispatch_source_cancel
取消源事件
定时器监听
倒计时案例:
- (void)iTimer {
__block int timeout = 60;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
if(timeout <= 0) {
dispatch_source_cancel(_timer);
} else {
timeout--;
NSLog(@"倒计时:%d", timeout);
}
});
dispatch_resume(_timer);
}
自定义事件,变量增加
变量增加案例:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *iBt;
@property (weak, nonatomic) IBOutlet UIProgressView *iProgress;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, assign) NSUInteger totalComplete;
@property (nonatomic ,assign) int iNum;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.totalComplete = 0;
self.queue = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL);
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(self.source, ^{
NSUInteger value = dispatch_source_get_data(self.source); // 每次去获取iNum的值
self.totalComplete += value;
NSLog(@"进度: %.2f",self.totalComplete/100.0);
self.iProgress.progress = self.totalComplete/100.0;
});
// [self iTimer];
}
- (IBAction)btClick:(id)sender {
if ([self.iBt.titleLabel.text isEqualToString:@"开始"]) {
dispatch_resume(self.source);
NSLog(@"开始了");
self.iNum = 1;
[sender setTitle:@"暂停" forState:UIControlStateNormal];
for (int i= 0; i<1000; i++) {
dispatch_async(self.queue, ^{
sleep(1);
dispatch_source_merge_data(self.source, self.iNum); // 传递iNum触发hander
});
}
} else {
dispatch_suspend(self.source);
NSLog(@"暂停了");
self.iNum = 0;
[sender setTitle:@"开始" forState:UIControlStateNormal];
}
}
@end
附上dispatch_source的Demo
网友评论