练习题
- (void)viewDidLoad {
[super viewDidLoad];
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a++;
});
}
NSLog(@"a = %d", a);
}
A、 a = 0 B、a < 5 C、a = 5 D、a > 5
答案:C、D
答案分析
- while 循环里面的代码是异步函数,并且整个代码块是一个耗时操作,所以在执行的时候并不会等到整个代码执行完后再进入下一个循环,而是直接进入下一个循环
- 每次执行一次代码块,因为是异步函数,所以每次都会新开启一个线程来执行任务
- a 的初始值为 0,当还没执行完 dispatch_async 时,a 都是为 0 ,并且 a++ 会被执行很多次
- 当 a++ 所在整个代码被第一次执行完时,a = 1,当 a 的值被修改时,由于 a 的地址只有一个,也就是修改的也是同一个内存空间,所以其他线程的 a 的值也会被修改
- 当 a 的值第一次大于 5 时,就会退出循环,但此时还有很多其他线程任务的 a++ 还没完全执行完,所以在退出循环之后,a 的值还依旧会递增
- 所以 a 打印的结果就会大于等于 5 。
打印结果:
看到打印结果很明显浪费了很多性能,想直接打到正确的结果还是有一定的风险,要解决这风险我们可以尝试一下使用信号量
//当value 等于 1 时,会起到锁的效果,一次一个执行。
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
// timeout = DISPATCH_TIME_FOREVER ,意味没有回来,就一直等待,加锁的效果
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
//解锁
dispatch_semaphore_signal(sem);
优化代码
- (void)viewDidLoad {
[super viewDidLoad];
//dispatch_semaphore_create(long value);当value 等于 1 时,会起到锁的效果,一次一个执行。
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
__block int a = 0;
while (a < 5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"里面 a = %d----%@", a, [NSThread currentThread]);
a++;
dispatch_semaphore_signal(sem);
});
// timeout = DISPATCH_TIME_FOREVER ,意味没有回来,就一直等待
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//
}
NSLog(@"a = %d", a);
}
打印结果:
答案解析
- 信号量的目的就是为了每执行一次while 循环的时候,异步代码块也执行一次
- 由于是这是异步代码块,所以先回执行代码块后面的代码,然后才会开启一个新线程执行块里面的代码,当 a++ 执行完了才是整个代码块执行的结束。
- 所以在要执行代码块是便加上信号锁,当整个代码块意味结束时,再解锁,于是便不会消耗多余的性能。
栅栏
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
主要起到控制任务执行顺序,起到同步效果
-
dispatch_barrier_async
前面的任务执行完毕才会到这里
-
dispatch_barrier_sync
作用相同,但是这个会堵塞线程,影响后面的任务执行
-
注意,栅栏函数只能控制同一并发队列
基本使用
- (void)demo2{
dispatch_queue_t concurrentQueue = dispatch_queue_create("janice", DISPATCH_QUEUE_CONCURRENT);
/* 1. 异步函数 */
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
/* 2. 栅栏函数 */
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
NSLog(@"加载那么多,喘口气!!!");
});
NSLog(@"************起来干!!");
}
打印结果:
- 因为 1,2,3 都是异步函数,所以先打印了 ‘起来干’
- 因为 2 是栅栏函数,所以要等 1 执行玩了之后才会执行 2 , 3 要等栅栏函数执行完了才能被执行。
实际使用场景一
- (void)demo3{
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// signal -- 线程BUG
for (int i = 0; i<2000; 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];
});
}
}
打印结果
有个问题,添加了2000张图片,但是实际打印效果只有 1997,这是为什么呢,说明线程不安全。
优化办法,在图片加入到数组的时候,添加一个栅栏
- (void)demo3{
// 顺序执行
// 线程安全
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// signal -- 线程BUG
for (int i = 0; i<2000; 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];
dispatch_barrier_async(concurrentQueue, ^{
[self.mArray addObject:image];
});
});
}
}
打印结果:
就是添加一个栅栏来保障其线程安全
实际使用场景二
将自定义并发队列换成全局并发队列。
- (void)demo3{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// signal -- 线程BUG
for (int i = 0; i<2000; 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];
dispatch_barrier_async(concurrentQueue, ^{
[self.mArray addObject:image];
});
});
}
}
崩溃了,使用栅栏函数的时候,一定要注意,一定是自定义的并发队列,全局队列是整个系统都在用,在这了使用的情景会就会造成堵塞,所以就会崩溃
总结:
- 栅栏函数能够起到同步效果
- 栅栏函数还能起到安全性的效果
- 堵塞队列
- 在堵塞队列的时候要使用自己的队列不要使用全局的
- 使用同一个队列,才能起到效果
调度组
作用:控制任务执行顺序
- dispatch_group_create 创建组
- dispatch_group_async 进组任务
- dispatch_group_notify 进组任务执行完毕通知
- dispatch_group_wait 进组任务执行等待时间
- dispatch_group_enter 进组
- dispatch_group_leave 出组
- 注意搭配使用
基本使用
- (void)groupDemo{
//创建调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
// SIGNAL
dispatch_group_async(group, queue, ^{
NSString *logoStr = @"http://p.qpic.cn/qqcourse/QFzQYCgCrxlq7n5Jats36WGb4wxzJIYmFplnUUgdxk4gy66xicbmGCDM5DXDuVNQP/";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
dispatch_group_async(group, queue1, ^{
sleep(2);
NSString *logoStr = @"https://www.baidu.com/img/baidu_resultlogo@2.png";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:logoStr]];
UIImage *image = [UIImage imageWithData:data];
[self.mArray addObject:image];
});
__block UIImage *newImage = nil;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"数组个数:%ld",self.mArray.count);
for (int i = 0; i<self.mArray.count; i++) {
UIImage *waterImage = self.mArray[i];
newImage = [KC_ImageTool kc_WaterImageWithWaterImage:waterImage backImage:newImage waterImageRect:CGRectMake(20, 100*(i+1), 100, 40)];
}
self.imageView.image = newImage;
});
}
信号量
dispatch_semaphore_t
- dispatch_semaphore_create 创建信号量
- dispatch_semaphore_wait 信号量等待
- dispatch_semaphore_signal 信号量释放
- 控制 GCD 并发数
基本使用
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 信号量 -- gcd控制并发数
// 同步
//总结:由于设定的信号值为3,先执行三个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//任务1
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务1");
sleep(1);
NSLog(@"任务1完成");
dispatch_semaphore_signal(semaphore);
});
//任务2
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务2");
sleep(1);
NSLog(@"任务2完成");
dispatch_semaphore_signal(semaphore);
});
//任务3
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行任务3");
sleep(1);
NSLog(@"任务3完成");
dispatch_semaphore_signal(semaphore);
});
}
打印结果:
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 挂起
基本使用
ViewController.m
#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.tz.cn.janice", 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 值响应
});
}
}
打印结果: