先看一个面试题
__block int a = 0;
while (a<5) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"里面 a=%d--%@",a,[NSThread currentThread]);
a++;
});
}
NSLog(@"外面 a=%d",a);
A: 0 B: <5 C: =5 D: >5
结果为CD,=5或者>5。注意异步队列在while循环里,所以不能往下走。while里面开启异步耗时操作,while正常循环,可能一个循环过后,添加到队列里面的任务还没执行,a还没有++,所以当a=5的时候while里面循环了多少次不确定,但是当a=5的时候才会终止循环,当终止循环后,代码会往下执行,然后打印a,但是终止循环到打印a的这段时间可能并发队列里面的任务有几个正好执行完了,这样就会导致a的值又变大了,所以从外面打印的a大于或者等于5.
屏幕快照 2020-09-23 下午2.24.16.png
这样导致的问题有两个
1.浪费这么多性能
2.最终的a值无法确定
下面我们用信号量的方法处理一下
信号量 dispatch_semaphore_t
通过信号量可以控制不同线程任务的执行顺序和依赖关系,从而达到线程同步的目的。
信号量的使用主要有3个方法来搭配。
1.dispatch_semaphore_create(value):此方法是用来创建信号量,通常加锁的操作时,此时入参0。
2.dispatch_semaphore_wait(): 此方法是等待信号量,会对信号量减1(value - 1),当信号量 < 0时,会阻塞当前线程,等待信号(signal),当信号量 >= 0时,会执行wait后面的代码。
3.dispatch_semaphore_signal(): 此方法是信号量加1,当信号量 >= 0 会执行wait之后的代码
这3个方法必须搭配使用,缺一不可。
dispatch_semaphore_wait()和dispatch_semaphore_signal()是成对使用的。
接下来我们用信号量加锁解决上面的问题,具体代码如下:
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
__block int a = 0;
while (a<10) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"里面 a=%d--%@",a,[NSThread currentThread]);
a++;
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"外面 a=%d",a);
打印结果如下:
2020-10-12 21:40:53.886513+0800 001---函数与队列[10182:3358757] 里面 a=0--<NSThread: 0x600001b696c0>{number = 3, name = (null)}
2020-10-12 21:40:53.886523+0800 001---函数与队列[10182:3358751] 里面 a=0--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.886748+0800 001---函数与队列[10182:3358757] 里面 a=1--<NSThread: 0x600001b696c0>{number = 3, name = (null)}
2020-10-12 21:40:53.886793+0800 001---函数与队列[10182:3358751] 里面 a=2--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.886919+0800 001---函数与队列[10182:3358751] 里面 a=4--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.886983+0800 001---函数与队列[10182:3358757] 里面 a=4--<NSThread: 0x600001b696c0>{number = 3, name = (null)}
2020-10-12 21:40:53.887066+0800 001---函数与队列[10182:3358751] 里面 a=5--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.887096+0800 001---函数与队列[10182:3358757] 里面 a=6--<NSThread: 0x600001b696c0>{number = 3, name = (null)}
2020-10-12 21:40:53.887213+0800 001---函数与队列[10182:3358751] 里面 a=7--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.889660+0800 001---函数与队列[10182:3358757] 里面 a=8--<NSThread: 0x600001b696c0>{number = 3, name = (null)}
2020-10-12 21:40:53.890100+0800 001---函数与队列[10182:3358751] 里面 a=9--<NSThread: 0x600001b6cb80>{number = 6, name = (null)}
2020-10-12 21:40:53.890302+0800 001---函数与队列[10182:3358601] 外面 a=10
栅栏函数 dispatch_barrier_sync/dispatch_barrier_async
当我们有两组异步任务需要有先后顺序的时候,可以使用栅栏函数解决。有两个api方法,dispatch_barrier_sync和dispatch_barrier_async,等到dispatch_barrier_async或dispatch_barrier_sync方法前的任务执行完毕后才会去执行后边追加到队列中的任务,简单来说dispatch_barrier_async或dispatch_barrier_sync将异步任务分成了两个组,执行完第一组后,再执行自己,然后执行队列中剩余的任务。唯一不同的是dispatch_barrier_async不会阻塞线程。具体使用如下:
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"----------------------%@--------",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"加载那么多,喘口气");
});
NSLog(@"********起来干");
打印结果
2020-10-12 22:00:53.824550+0800 004--GCD进阶使用[10306:3369587] 123
2020-10-12 22:00:53.825133+0800 004--GCD进阶使用[10306:3369518] ----------------------<NSThread: 0x600003e01cc0>{number = 1, name = main}--------
2020-10-12 22:00:53.825448+0800 004--GCD进阶使用[10306:3369518] ********起来干
2020-10-12 22:00:53.825465+0800 004--GCD进阶使用[10306:3369587] 加载那么多,喘口气
如果换成这个方法dispatch_barrier_async,具体代码如下
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(1);
NSLog(@"123");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"----------------------%@--------",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"加载那么多,喘口气");
});
NSLog(@"********起来干");
打印结果为:
2020-10-12 22:09:36.613491+0800 004--GCD进阶使用[10336:3374328] ********起来干
2020-10-12 22:09:37.613944+0800 004--GCD进阶使用[10336:3374484] 123
2020-10-12 22:09:37.614273+0800 004--GCD进阶使用[10336:3374484] ----------------------<NSThread: 0x600001be2780>{number = 7, name = (null)}--------
2020-10-12 22:09:37.614427+0800 004--GCD进阶使用[10336:3374484] 加载那么多,喘口气
注意:
栅栏函数只能控制同一并发队列。所以在网络请求AFN中使用栅栏不起作用
栅栏函数其实是作为堵塞队列而存在的(同步函数是作为堵塞线程存在的)
栅栏函数不能用于全局并发队列,只能用于自定义的并发队列,因为全局队列系统也在用,假如用栅栏函数将全局并发队列堵塞住了,会导致系统运行不正常而导致崩溃。
可变数组 栅栏函数解决线程不安全
看下面的例子:
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// signal -- 线程BUG
for (int i = 0; i<5000; 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];
});
}
这里创建这么多线程同时对可变数据操作,造成线程不安全,容易造成崩溃,可以使用栅栏函数解决。
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// signal -- 线程BUG
for (int i = 0; i<5000; 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_t
dispatch_group_t 在日常开发中最直接的作用是控制任务执行顺序。
调度组的使用
dispatch_group_create 创建组
dispatch_group_async 进组任务
dispatch_group_notify 进组任务执行完毕通知
dispatch_group_wait 进组任务执行等待时间
dispatch_group_enter 进组
dispatch_group_leave 出组
dispatch_group_enter 和 dispatch_group_leave必须成对使用
dispatch_group_async
通过以下代码可以发现,无论任务一和任务儿耗时多少,都会在全部执行结束后,调用dispatch_group_notify方法,我们一般在此方法中获取到dispatch_get_main_queue主线程,用来刷新UI等操作。
//创建调度组
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.group", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"任务一");
});
dispatch_group_async(group, queue1, ^{
NSLog(@"任务二");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务执行完成");
});
打印结果
2020-10-13 22:12:22.163405+0800 005---GCD进阶使用(下)[10816:3415753] 任务二
2020-10-13 22:12:24.166883+0800 005---GCD进阶使用(下)[10816:3415749] 任务一
2020-10-13 22:12:24.167300+0800 005---GCD进阶使用(下)[10816:3415694] 任务执行完成
dispatch_group_enter 和 dispatch_group_leave
使用进出组的方式和dispatch_group_async效果相同,只不过是有了进出组的代码,逻辑更加清晰明了。而且dispatch_group_enter和dispatch_group_leave是成对使用的,必须先进组再出组,缺一不可。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"第一个走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二个走完了");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
打印结果
2020-10-13 22:21:13.499500+0800 005---GCD进阶使用(下)[10858:3421125] 第二个走完了
2020-10-13 22:21:15.504329+0800 005---GCD进阶使用(下)[10858:3421124] 第一个走完了
2020-10-13 22:21:15.504693+0800 005---GCD进阶使用(下)[10858:3421072] 所有任务完成,可以更新UI
dispatch_group_wait 设置等待时间,区分异步任务执行
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout) 需要传入两个参数,dispatch_time_t timeout:参数用来指定等待的时间。
这里的等待表示,一旦调用dispatch_group_wait函数,该函数就处理调用的状态而不返回值,只有当函数的currentThread停止,或到达wait函数指定的等待的时间,或Dispatch Group中的操作全部执行完毕之前,执行该函数的线程停止.
当指定timeout为DISPATCH_TIME_FOREVER时就意味着永久等待
当指定timeout为DISPATCH_TIME_NOW时就意味不用任何等待即可判定属于Dispatch Group的处理是否全部执行结束
如果dispatch_group_wait函数返回值不为0,就意味着虽然经过了指定的时间,但Dispatch Group中的操作并未全部执行完毕
如果dispatch_group_wait函数返回值为0,就意味着Dispatch Group中的操作全部执行完毕。
这里的应用场景可以在任务一和任务二两个异步任务,也要有先后的执行顺序时,通过wait来阻塞当前线程,只有当执行完一组任务或者超过超时时间后才可以继续向下进行。
通过一个例子来看一下。通过打印结果可以发现,当执行的之间为DISPATCH_TIME_FOREVER或者未超时时,是先执行了任务一后,才执行的任务二,然后回到主线程的。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"任务一");
dispatch_group_leave(group);
});
// long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,3 * NSEC_PER_SEC));
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_FOREVER, 0));
if (timeout == 0) {
NSLog(@"回来了");
}else{
NSLog(@"等待中 -- 转菊花");
}
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"任务二");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
打印结果
2020-10-13 22:27:13.708640+0800 005---GCD进阶使用(下)[10875:3424596] 任务一
2020-10-13 22:27:13.709019+0800 005---GCD进阶使用(下)[10875:3424432] 回来了
2020-10-13 22:27:13.709438+0800 005---GCD进阶使用(下)[10875:3424596] 任务二
2020-10-13 22:27:13.735163+0800 005---GCD进阶使用(下)[10875:3424432] 所有任务完成,可以更新UI
这里修改一下等待时间,改为long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,1 * NSEC_PER_SEC));打印结果为
2020-10-13 22:30:15.981184+0800 005---GCD进阶使用(下)[10894:3426454] 等待中 -- 转菊花
2020-10-13 22:30:15.981576+0800 005---GCD进阶使用(下)[10894:3426516] 任务二
2020-10-13 22:30:16.981933+0800 005---GCD进阶使用(下)[10894:3426518] 任务一
2020-10-13 22:30:16.982324+0800 005---GCD进阶使用(下)[10894:3426454] 所有任务完成,可以更新UI
dispatch_source_t
dispatch_source
是一种基本的数据类型,可以用来监听一些底层的系统事件,在日常的开发中,我们经常使用它来创建一个GCD
的Timer
。但是它还有很多其他的监听类型,通过查看官方文档我们可得以下监听:
-
Timer Dispatch Source
:定时调度源。 -
Signal Dispatch Source
:监听UNIX信号调度源,比如监听代表挂起指令的SIGSTOP信号。 -
Descriptor Dispatch Source
:监听文件相关操作和Socket
相关操作的调度源。 -
Process Dispatch Source
:监听进程相关状态的调度源。 -
Mach port Dispatch Source
:监听Mach相关事件的调度源。 -
Custom Dispatch Source
:监听自定义事件的调度源
主要的API如下:
dispatch_source_create: 创建事件源
dispatch_source_set_event_handler: 设置数据源回调
dispatch_source_merge_data: 设置事件源数据
dispatch_source_get_data: 获取事件源数据
dispatch_resume: 继续
dispatch_suspend: 挂起
dispatch_cancle: 取消
GCD定时器
在iOS开发中使用定时器一般会用NSTimer,但NSTimer是依赖Runloop的,而Runloop可以运行在不同的模式下,如果NSTimer添加在一种模式下,当Runloop运行在其他模式下的时候,定时器就“不走”了,因此NSTimer在计时上会有误差,并不是特别精确,而GCD定时器不依赖Runloop,计时精度要高很多。
@interface GCDTimer : NSObject
// 开始
- (void)start;
// 暂停
- (void)supend;
// 继续
- (void)Continue;
- (instancetype)initWithTimeInterval:(NSInteger) TimeInterval block:(void (^)(NSInteger timerNum))block ;
@end
@interface GCDTimer()
@property (nonatomic,assign)NSInteger timeInterval;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, copy) void(^block)(NSInteger timerNum);
@property (nonatomic) BOOL isRunning;
@end
@implementation GCDTimer
- (instancetype)initWithTimeInterval:(NSInteger) TimeInterval block:(void (^)(NSInteger timerNum))block{
if (self == [super init]) {
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
self.queue = dispatch_queue_create("com.tz.cn.cooci", 0);
self.isRunning = YES;
self.timeInterval = TimeInterval;
self.block = block;
// 保存代码块 ---> 异步 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.timeInterval -= 1;
if ( self.timeInterval == 0) {
[self supend];
}
block(self.timeInterval);
});
dispatch_resume(self.source);
dispatch_source_set_cancel_handler(self.source, ^{
NSLog(@"---- %d",__LINE__);
});
}
return self;
}
// 开始
- (void)start{
dispatch_async(self.queue, ^{
if (!self.isRunning) {
NSLog(@"暂停下载");
return ;
}
dispatch_source_set_timer(self.source, DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC, 0);
});
}
// 继续
- (void)Continue{
dispatch_resume(self.source);
dispatch_resume(self.queue);
self.isRunning = YES;
}
// 暂停
- (void)supend{
dispatch_suspend(self.source);
dispatch_suspend(self.queue);// mainqueue 挂起
self.isRunning = NO;
}
@end
调用
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *button;
@property (strong, nonatomic)GCDTimer *gcdTimer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GCDTimer *gcdTimer = [[GCDTimer alloc]initWithTimeInterval:60 block:^(NSInteger timerNum) {
[self.button setTitle:[NSString stringWithFormat:@"倒计时:%ld 秒",timerNum] forState:UIControlStateNormal];
}];
self.gcdTimer = gcdTimer;
// [BYTimer timerWithTimeInterval:60 block:^(BYTimer *timer) {
//
// }];
}
// 开始
- (IBAction)beginBtnClick:(UIButton *)sender {
[self.gcdTimer start];
}
// 暂停
- (IBAction)supendClick:(UIButton *)sender {
[self.gcdTimer supend];
}
// 继续
- (IBAction)continueClick:(UIButton *)sender {
[self.gcdTimer Continue];
}
网友评论