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_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)(栅栏函数栅不住全局队列的缘故原由就在这里,由于它指定的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_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的源码分析
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_async函数以外的方式显式地从组中添加和删除任务,那么利用这个函数(与dispatch_group_enter一起利用)答应您的应用步调精确地管理任务引用计数。
对该函数的调用必须平衡对dispatch_group_enter的调用。调用它的次数凌驾dispatch_group_enter是无效的,这会导致负的计数。
3.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的源码声明:
- 当两个线程必要协调特定变乱的完成时,为该值转达0很有效;
- 转达大于0的值对于管理有限的资源池很有效,此中池大小便是该值;
- 信号量的起始值转达小于信号量的起始值。 转达小于零的值将导致返回 NULL,也就是小于0就不会正常实验。
总的来说:信号量初始值可以控制线程池中的最多并发数目
2.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的源码声明:
- 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)btClickid)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 |