关于多线程的概念网上很多资料可供学习,下面我们主要讲解几种工作中会经常遇到的多线程问题及解决思路。
一、一句话简单理解相关概念
进程:手机中的一个APP就是一个进程。
线程:一个进程可以开启多个线程,默认开启主线程。
队列: 队形结构。分串行和并发,按照 FIFO(先进先调度)执行任务。
同步: 按顺序执行,不开启新线程。
异步: 不按顺序执行,可以开启新线程。
串行: 在线程中依次执行。
并发: 多条线程同时开工。
-
GCD应用
1. 经典现象之死锁
- (void)test{
NSLog(@"-----任务1----");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"-----任务2----");
});
NSLog(@"-----任务3----");
}
分析:控制台打印"-----任务1----"后崩溃。因为默认就一个主线程和一个主队列,任务2要等待test执行完才执行,而test的执行又依赖于任务2,所以出现相互等待,造成崩溃。
2. 经典场景之卖票
- (void)viewDidLoad {
[super viewDidLoad];
// 第一个线程卖票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saleTickets];
});
// 第一个线程卖票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self saleTickets];
});
}
// 售票
- (void)saleTickets {
while (self.tickets > 0) {
//模拟延迟
[NSThread sleepForTimeInterval:1];
//判断是否还有余票
if (self.tickets > 0) {
//如果有票,卖一张,提示用户
self.tickets--;
NSLog(@"剩余票数 %zd %@",self.tickets,[NSThread currentThread]);
}else{
NSLog(@"没有票,来晚了%@",[NSThread currentThread]);
break;
}
}
}
- 2.1 控制台部分输出,可以看到出现了资源抢夺
剩余票数 10 <NSThread: 0x600000970700>{number = 4, name = (null)}
剩余票数 11 <NSThread: 0x600000978140>{number = 3, name = (null)}
剩余票数 9 <NSThread: 0x600000970700>{number = 4, name = (null)}
剩余票数 9 <NSThread: 0x600000978140>{number = 3, name = (null)}
- 2.2 用同步串行解决如下,当然这里也可以使用 @synchronized加锁进行实现,苹果并不推荐使用加锁方式。
while (self.tickets > 0) {
//同步串行
dispatch_sync(dispatch_queue_create("Qinz", DISPATCH_QUEUE_SERIAL), ^{
//模拟延迟
[NSThread sleepForTimeInterval:1];
//判断是否还有余票
if (self.tickets > 0) {
//如果有票,卖一张,提示用户
self.tickets--;
NSLog(@"剩余票数 %zd %@",self.tickets,[NSThread currentThread]);
}else{
NSLog(@"没有票,来晚了%@",[NSThread currentThread]);
}
});
}
3. 使用栅栏函数解决常见多任务依赖问题
-(void)barrierDemo{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.Qinz.cn", DISPATCH_QUEUE_CONCURRENT);
/** 异步函数,模拟多任务网络请求 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"我在下载图片%d---当前线程%@",i,[NSThread currentThread]);
}
});
/** 异步函数,模拟多任务网络请求 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"我在请求商品详情数据%d--当前线程%@",i,[NSThread currentThread]);
}
});
/** 栅栏函数:相当于堵塞前面的操作 */
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"前面的异步任务执行完毕-----------%@",[NSThread currentThread]);
});
/** 处理日常任务 */
dispatch_async(concurrentQueue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"日常任务处理%d--当前线程%@",i,[NSThread currentThread]);
}
});
}
栅栏函数使用注意:
- 一定要是自定义的并发队列,使用系统提供的全局并发队列会达不到想要的效果。
- 必须要求任务都在同一个队列中执行。
4. 使用调度组解决多任务依赖问题
-(void)gcdGroup{
//创建调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0; i<500; i++) {
NSLog(@"我在下载图片%d---当前线程%@",i,[NSThread currentThread]);
}
});
//允许等待的任务执行的时间
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,3 * NSEC_PER_SEC));
//如果dispatch_group_wait函数返回值为0,就意味着Dispatch Group中的操作全部执行完毕
if (timeout == 0) {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"---刷新UI----");
});
}else{
NSLog(@"---你超时了----");
}
}
5. 使用GCD的_enter和_leave解决多任务依赖问题
-(void)gcdEnter{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
//底层有一个signal,进组就会+1.判断signal是否为0,出组signal=0
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i<10; i++) {
NSLog(@"我在下载图片%d---当前线程%@",i,[NSThread currentThread]);
}
NSLog(@"🍺---第一个任务执行完毕 ----🍺");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
for (int i = 0; i<8; i++) {
NSLog(@"我在请求商品详情数据%d--当前线程%@",i,[NSThread currentThread]);
}
NSLog(@"🌹---第二个任务执行完毕 ----🌹");
dispatch_group_leave(group);
});
//信号为0,才进入通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,可以更新UI了");
});
}
注意:_enter和_leave要成对出现,否则会造成最终不走通知回调或程序崩溃。
6. 当我们要控制并发数时,使用信号量来进行实现
#pragma mark - 信号量控制并发数
-(void)gcdSemaphore{
/**
通过信号量来控制并发数,当信号量为2时,最大允许2两条线程同时执行;当信号量为1时,可以达到加锁的目的,实现同步效果。
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//任务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);
});
}
-
NSOperation应用
1. 线程之间的通讯
#pragma mark - 线程通讯
-(void)communication{
NSOperationQueue*queue = [[NSOperationQueue alloc]init];
queue.name = @"Qinz";
[queue addOperationWithBlock:^{
NSLog(@"🍺当前队列---%@-当前线程---%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
//模拟网路请求
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"h🌹当前队列---%@-当前线程---%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}
2. 控制并发数
#pragma mark - 控制并发数
-(void)concurrent{
NSOperationQueue*quue = [[NSOperationQueue alloc]init];
//控制并发数
quue.maxConcurrentOperationCount = 3;
for (int i = 0; i < 30; i++) {
[quue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"🍺--%d--当前线程---%@",i,[NSThread currentThread]);
}];
}
}
3. 解决多任务依赖问题
#pragma mark - 多任务依赖顺序执行
-(void)depend{
NSBlockOperation * bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"---- 请求数据,拿到token ------");
}];
NSBlockOperation * bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"----- 拿到token,请求数据1 ------");
}];
NSBlockOperation * bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1];
NSLog(@"------ 拿到1,请求数据2 -----");
}];
//建立依赖
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"--- 所有任务执行完毕 --------");
}
多线程还有很多用法值得我们去探索,以上只是针对实际开发常见问题进行分析和给出解决思路。
我是Qinz,希望我的文章对你有帮助。