多线程GCD
概念
GCD全称Grand Central Dispatch
纯c语言,提供了非常多强大的函数
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(双核,四核)
GCD会自动管理线程的生命周期(创建线程,调度线程,销毁线程)
只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
执行任务的函数有同步函数和异步函数
同步函数
同步 dispatch_sync
特点:
- 必须等待当前语句执行完成之后,才会执行吓一跳语句
- 不会开启新的线程
- 在当前执行block的任务
异步函数
异步 dispatch_async
特点:
- 不用等待当前任务执行完毕,就可以执行下一条语句
- 会开启线程执行block中的任务
- 异步是多线程的代名词
队列也分为两种,串行和并行队列
队列是一种数据结构,遵循FIFO(first in first out)原则
串行队列,一次只能执行一个任务,并且任务是一个一个的按照顺序来执行的
并行队列,一个执行多个任务,任务的执行完成时间不可控制,跟任务复杂度和cpu调度有关系
下边来一张图进行一个对比
函数和队列组合进行任务执行的几种情况
1 同步函数+串行队列
- 不会开启新线程,在当前线程执行任务
- 任务一个接一个的执行
- 会产生阻塞
2 同步函数+并发队列
- 不会开启线程,在当前线程执行任务
- 任务一个接一个执行
3 异步函数+ 串行队列
- 会开启新的线程,
- 任务一个接一个执行
4 异步函数+并行队列
- 会开启新线程
- 任务异步执行没有顺序,cpu调度有关
主队列
- 专门用来在主线程上调度任务的队列
- 不会开启新线程
- 如果当前主线程正在执行任务,那么无论主队列中添加什么任务都不会被调度
- dispatch_get_main_queue()
全局队列
- 为了方便开发者使用,苹果提供了全局队列,dispatch_get_global_queue(0,0)
- 是一个并发队列
- 多线程开发时,如果对队列没有特殊要求,在执行异步任务时,可以直接试用全局队列
函数和队列的应用
来先看一道微博面试题
- (void)wbinterDemo{
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// sleep(2);
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 堵塞 - 护犊子
dispatch_sync(queue, ^{
NSLog(@"3");
});
// **********************
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
// A: 1230789
// B: 1237890
// C: 3120798
// D: 2137890
}
分析:
- 看3任务,是同步函数,他会阻塞线程,所以0肯定在3后边
- 因为0是在主线程同步执行,所以789肯定在0后边,789是异步任务,所以顺序不一定
综上原因分析,最终答案为A C
栅栏函数dispatch_barrier
现在一个需求,任务1跟任务2,等1,2执行完之后打印.这个时候我们就可以利用栅栏函数来实现
dispatch_queue_t concurrentQueue = dispatch_queue_create("aaaa", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"任务1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"任务2");
});
//阻塞队列
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"任务1,2执行完了");
});
NSLog(@"************起来干!!");
问题 dispatch_barrier_async
换成dispatch_barrier_sync
会有什么不一样呢
dispatch_barrier_sync
不光会阻塞队列还会阻塞线程,NSLog(@"************起来干!!");
打印时机会提前
可变数组线程不安全,解决办法
dispatch_queue_t concurrentQueue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t concurrentQueue = dispatch_get_global_queue(0, 0);
// signal -- 线程BUG
for (int i = 0; I<10000; 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];
// });
});
}
报错内容
Thread 8: signal SIGABRT
malloc: *** error for object 0x7ff105601a00: pointer being freed was not allocated
malloc: *** set a breakpoint in malloc_error_break to debug
运行这段代码会发现当循环次数较大时可能会直接崩溃,崩溃原因是,多个线程同时访问数组往数组里边加元素,此时数组是不安全的,如何解决这个问题
//可以加锁
@synchronized (self) {
[self.mArray addObject:image];
}
//还可以加一个栅栏函数
dispatch_barrier_async(concurrentQueue, ^{
[self.mArray addObject:image];
});
坑点队列换成全局并发队列dispatch_get_global_queue
,发现也会崩溃
全局并发队列不止你一个人在使用,系统也在使用,所以全局队列不能堵塞
注意 :
- 栅栏函数一定要使用自定义的队列
- 栅栏函数使用的队列一定要是同一个队列(afn请求使用栅栏发现没有效果,因为afn内部有自己的一个队列.不是同一个队列)
- dispatch_barrier_sync会阻塞线程一定要注意
调度组 dispatch_group
dispatch_group_notify
dispatch_group_notify
当组中的任务执行完成会来到这个函数
//创建调度组
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.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"来了1");
});
dispatch_group_async(group, queue1, ^{
sleep(2);
NSLog(@"来了2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"执行完了");
});
Group进组出组另一种写法
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// dispatch_group_async -- 下节课的源码分析 --封装意思
dispatch_async(queue, ^{
NSLog(@"第一个走完了");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"第二个走完了");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新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中的操作全部执行完毕
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,1 * NSEC_PER_SEC));
if (timeout == 0) {
NSLog(@"回来了");
}else{
NSLog(@"等待中 -- 转菊花");
}
__block UIImage *newImage = nil;
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI");
});
信号量dispatch_semaphore_t
信号量可以控制线程之间的执行顺序和依赖关系,从而达到线程同步的目的
- dispatch_semaphore_create(value) 创建信号量 value是信号量的数量
- dispatch_semaphore_wait() 等待信号量,对信号量数量-1,当信号量<=0 时会阻塞当前线程,>0时执行waite之后的代码
- dispatch_semaphore_signal 发送信号,将信号量+1
至于value的值根据实际需求进行设置比如
- 停车场进车,开始时车位肯定是充足的所以此时value肯定不能为0,车进的时候看value是否为0,为0就在这等待,当一辆车出去的时候发送一个消息信号量+1,这时等待的车辆收到消息进入
- 比如我要买一个煎饼果子,刚开始老板还没做所以value为0 ,当顾客要买的时候先等待老板制作,制作好了信号量+1,顾客买走信号量-1,下一个顾客来买时继续等待制作
使用的时候一定要注意死锁问题
使用示例
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建信号量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// 添加任务1
dispatch_async(globalQueue, ^{
sleep(1);
NSLog(@"task 1");
// 任务1执行完毕,信号量 = -1 + 1 = 0,继续执行wait之后的代码
dispatch_semaphore_signal(semaphore);
});
NSLog(@"wait task 1...");
// 信号量 = 0 - 1 < 0,线程被阻塞,等待任务1执行完毕
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 添加任务2
dispatch_async(globalQueue, ^{
NSLog(@"task 2");
dispatch_semaphore_signal(semaphore);
});
NSLog(@"wait task 2...");
// 阻塞线程,等待任务2结束
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 所有任务结束
NSLog(@"All tasks done!");
}