函数与队列
把任务添加到队列,并且指定执行任务的函数
- 任务使用block封装,且任务的block没有参数也没有返回值
- 执行任务的函数
- 异步
dispatch_async{}
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行block的任务
- 异步是多线程的代名词
- 同步
dispatch_sync{}
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前执行block的任务
还原最基本的写法:
- 异步
// 把任务添加到队列 --> 函数
// 任务
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.zb.cn", NULL);
// 函数
dispatch_async(queue, block);
队列:
特殊的两种队列:
主队列 dispatch_get_main_queue()
- 专门用来在主线程上调度任务的队列,是串行队列
- 不会开启线程
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
全局队列 dispatch_get_global_queue()
- 全局队列是一个并发队列
- 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
队列与函数:
理解上面几种组合后,尝试解答出下面的任务输出顺序、、
问题一
- (void)textOne {
dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"任务1");
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_async(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
首先明确是一个并发队列,里面有任务1
、dispatch_async
block任务、任务5
,所以按顺序输出任务1
、任务5
;里面嵌套的异步操作和外面的分析一模一样,即整个的输出顺序为1、5、2、4、3
问题二
- (void)textTwo {
dispatch_queue_t queue = dispatch_queue_create("zb", DISPATCH_QUEUE_SERIAL);
NSLog(@"任务1");
dispatch_async(queue, ^{
NSLog(@"任务2");
dispatch_sync(queue, ^{
NSLog(@"任务3");
});
NSLog(@"任务4");
});
NSLog(@"任务5");
}
这里是一个串行队列,dispatch_async
任务开启了一个线程专门处理,不必等待,所以先按顺序输出任务1
、任务5
;进入第一个dispatch_async
任务,串行队列,所以也是按顺序执行任务2
、dispatch_async
block任务、任务4
;此时的block任务是一个同步函数,所以当任务2
执行完毕以后,走到这个发现是同步,然后就把任务3
加入到队列里执行,此时队列里的任务是任务2
、dispatch_async
block任务、任务4
、任务3
;根据 FIFO
原则正常行走,任务2
结束后,执行dispatch_async
block任务,但是因为同步的原因,执行这个block任务又必须要执行任务3
,执行任务3
的前提是任务4
执行结束,执行任务4
的前提是block任务执行结束,这里发生里死锁。所以任务的输出顺序为任务1
、任务5
、任务2
,然后奔溃。
死锁的产生
- 主线程因为同步函数的原因等着先执行任务
- 主队列等着主线程的任务执行完毕在执行自己的任务
- 主队列和主线程相互等待会造成死锁
如果把上面的串行队列改成并发队列,输出的结果又是什么样的呢?
下面看一个面试题
- (void)viewDidLoad {
[super viewDidLoad];
int a = 0;
while (a < 10) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a ++;
});
}
NSLog(@"%d",a);
}
问题一:a++ 报错的原因?
问题二:修改正确后,输出a=?
问题三:在不改变函数以及队列的前提下,如何让a的输出为10?
答案一:
__block
修饰a
的初始化,把a
的指针和值从栈区copy
到struct
,堆区。
答案二:输出结果a >= 10
,在while
循环里,每一次的循环都会产生一个线程,执行异步操作,不等待直接执行后面的任务,同时这也是耗时操作,所以在循环里可能会走很多次a++
操作
答案三:可以通过加锁的方式,实现输出a=10
具体代码如下
- (void)viewDidLoad {
[super viewDidLoad];
__block int a = 0;
// 信号量
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
while (a < 10) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
a ++;
dispatch_semaphore_signal(lock);
});
// 堵死
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
}
NSLog(@"%d",a);
}
GCD的使用
栅栏函数 dispatch_barrier_sync
- (void)demo2{
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
/* 1.异步函数 */
dispatch_async(concurrentQueue, ^{
for (NSUInteger i = 0; i < 5; i++) {
NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(concurrentQueue, ^{
for (NSUInteger i = 0; i < 5; i++) {
NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
}
});
/* 2. 栅栏函数 */
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"---------------------%@------------------------",[NSThread currentThread]);
});
NSLog(@"加载那么多,喘口气!!!");
/* 3. 异步函数 */
dispatch_async(concurrentQueue, ^{
for (NSUInteger i = 0; i < 5; i++) {
NSLog(@"日常处理3-%zd-%@",i,[NSThread currentThread]);
}
});
NSLog(@"**********起来干!!");
dispatch_async(concurrentQueue, ^{
for (NSUInteger i = 0; i < 5; i++) {
NSLog(@"日常处理4-%zd-%@",i,[NSThread currentThread]);
}
});
}
这里就达到了download1
、download2
任务完成后,才去执行日常处理3
、日常处理4
任务的效果。
提问1:如果把并发队列
dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
改成dispatch_get_global_queue(0,0);
后效果会怎么样?
执行代码后会神奇的发现,栅栏效果神奇的失效了。
这里需要注意了,栅栏函数一定要是自定义的并发队列,不然就无效,分析一下也可以得知,dispatch_get_global_queue
是全局的并发队列,加上栅栏实际上就是一个堵塞,如果有效的话,系统就。。GG了。
提问2:如果把
download1
或download2
任务的队列换成一个其他的队列,效果会怎么样?
执行代码后,也会发现,不在同一个队列的话,栅栏也是无效,所以这里也是一个需要注意的地方,必须要求都在同一个队列。
这是栅栏函数的第一个作用,保证顺序执行。
看下面代码:
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];
});
}
执行后,会发生crash,异步函数,创建了多条线程,同时对数组执行addObject
操作,造成资源抢夺,发送崩溃。
dispatch_queue_t concurrentQueue = dispatch_queue_create("zb", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5000; i++) {
dispatch_async(concurrentQueue, ^{
.
.
.
dispatch_barrier_async(concurrentQueue, ^{
[self.mArray addObject:image];
});
});
}
执行后,可以正常执行,输出结果。
第二个作用,保证线程安全。
调度组 group
创建组 dispatch_group_create
进组任务 dispatch_group_async
进组任务执行完毕通知: dispatch_group_notify
进组任务执行等待时间:dispatch_group_wait
或
进组 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_async(group, queue, ^{
NSLog(@"任务1");
});
long timeOut = dispatch_group_wait(group, 0.5);
dispatch_group_notify(group, queue, ^{
NSLog(@"任务2");
});
或
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"任务");
dispatch_group_leave(group);
});
使用enter
与leave
时,一定要成对出现,不然会产生crash。
信号量dispatch_semaphore_t
创建信号量 dispatch_semaphore_create
信号量等待 dispatch_semaphore_wait
信号量释放 dispatch_semaphore_signal
可以当作锁来使用,在本文一开始就使用了,还可以控制GCD最大并发数dispatch_semaphore_create(x)