多线程-GCD基本演练及死锁

200 阅读13分钟

一、iOS中的常见多线程方案

技术方案 简介 语言 线程生命周期 使用频率
pthread 1.一套通用的多线程API
2.适用于Unix/Linux/Windows等系统
3.跨平台/可移植
4.使用难度大
C 程序员管理 几乎不用
NSThread 1.使用更加面向对象
2.简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD 1.旨在替代NSThread等线程技术
2.充分利用设备多核
C 自动管理 经常使用
NSOperation 1.基于GCD(底层是GCD)
2.比GCD多了一些更简单实用的功能
3.使用更加面向对象
OC 自动管理 经常使用

二、相关术语

  • 在多线程中,有4个术语比较重要,同样也比较容易混淆:同步 异步 并发 串行
    • 同步异步主要影响:能不能开启新的线程
      • 同步不具备开启新线程的能力,在当前线程中执行任务。
      • 异步具备开启新线程的能力,但是不代表异步一定可以开启新的线程(如果队列是主队列,就不会开启新的线程)
    • 并发串行主要影响:任务的执行方式
      • 并发多个任务并发(同时)执行
      • 串行一个任务执行完毕后,再执行下一个任务

三、GCD的常用函数

  • GCD中有两个用来执行任务的函数

    • 同步的方式执行任务

      • dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    • 异步的方式执行任务

      • dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • GCD是开源的:GCD源码

四、GCD的队列

  • GCD的队列可以分为2大类型
    • 并发队列(Concurrent Dispatch Queue)
      • 可以让多个任务**并发(同时)**执行(自动开启多个线程同时执行任务)
      • 并发只有在异步(dispatch_async)函数下才有效
    • 串行队列(Serial Dispatch Queue)
      • 让任务一个接着一个的执行(一个任务执行完毕后,再执行下一个任务)

五、各种队列的执行效果

并发队列(获取全局并发队列/手动创建的并发队列) 手动创建的串行队列 主队列
同步(sync) 1. 不会开启新线程
2.串行执行任务
1. 不会开启新线程
2.串行执行任务
1.不会开启新线程
2.串行执行任务
异步(async) 1.会开启新线程
2.并发执行任务
1.会开启新线程
2.串行执行任务
1.不会开启新线程
2.串行执行任务
  • 注意:使用sync函数往当前串行队列中添加任务,会卡主当前串行队列(死锁),后面会举例说明。

六、举个栗子

6.1 基本演练(不包含主队列)

  • 并发队列有2种创建的方式(下面的栗子中使用手动创建的方式)
    • 获取全局的并发队列(系统为我们创建的) dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    • 手动创建并发队列(手动创建的)dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
  • 同步并发队列
    • 在当前线程执行任务(主线程)
    • 任务串行执行(先执行任务1,再执行任务2)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务2 - %@", [NSThread currentThread]);
        }
    });
}

打印结果:
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001bb5040>{number = 1, name = main}
  • 同步串行队列
    • 在当前线程执行(主线程)
    • 串行执行任务(先执行任务1,再执行任务2)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务2 - %@", [NSThread currentThread]);
        }
    });
}

打印结果:
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务1 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
执行任务2 - <NSThread: 0x600001fc5780>{number = 1, name = main}
  • 异步并发队列
    • 在子线程执行
    • 同时执行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务2 - %@", [NSThread currentThread]);
        }
    });
}

打印结果:
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600000e7b440>{number = 6, name = (null)}
执行任务1 - <NSThread: 0x600000e48700>{number = 4, name = (null)}
  • 异步串行队列
    • 在子线程执行
    • 串行执行任务(先执行任务1,再执行任务2)
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务2 - %@", [NSThread currentThread]);
        }
    });
}

打印结果:
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务1 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}
执行任务2 - <NSThread: 0x600001455c80>{number = 4, name = (null)}

6.2、主队列(特殊的串行队列)

  • 异步主队列
    • 主线程执行(主队列jishi)
    • 串行执行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务2 - %@", [NSThread currentThread]);
        }
    });
}

打印结果:
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务1 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
执行任务2 - <NSThread: 0x600000635040>{number = 1, name = main}
  • 同步主队列
    • 死锁
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{ // 任务1
        for (int i = 0; i < 5; i++) {
            NSLog(@"执行任务1 - %@", [NSThread currentThread]);
        }
    });

}
  • 首先理解队列、串行队列和并发队列

    • 队列:FIFO即First In First Out -> 先进先出
    • 串行队列:队列中的任务,当一个任务执行完,下一个任务才执行。
    • 并发队列:队列中的任务,会同时执行
  • 上面的代码中,同步主队列为什么会死锁

    • 主队列是一个串行队列,先进入队列中的是viewDidLoad任务
    • 在viewDidLoad任务后面,要同步执行一个任务1,即在当前线程(主线程)执行任务1,因为是同步串行执行任务,就要一个任务执行完,再继续执行下一个任务的执行
    • 因为viewDidLoad还没要执行完,所以任务1就没办法进行执行,而viewDidLoad想要执行完,就必须要等任务1执行完。这就形成了一种互相等待的状态。所以产生了死锁的现象。

七、相关面试题

7.1 以下代码会不会发生死锁现象?为什么?

  • 会,死锁的原因就是上面解释的主队列同步执行任务的原因
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"begin ------------");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务1");
    });
    NSLog(@"end ------------");

}

打印结果:
  begin ------------ 
  崩溃(死锁)

7.2 以下代码会不会发生死锁现象?为什么?

  • 不会,因为任务1是异步执行的,在主队列(串行队列)中,会同时执行viewDidLoad和任务1两个任务
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"begin ------------"); 
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{ 
        NSLog(@"执行任务1");
    });
    NSLog(@"end ------------");
}
打印结果:
begin ------------
end ------------
执行任务1

7.3 以下代码会不会发生死锁现象?为什么?

  • 会,因为子线程中的任务是在串行队列中执行的,在这个子线程中又在当前串行队列中进行同步操作,那么在串行队列中,就会形成子线程中的任务和同步任务互相等待的现象,所以会死锁。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"begin ------------");
    dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务1");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务2");
        });
        NSLog(@"执行任务3");
    });
    NSLog(@"end ------------");
}

打印结果:
begin ------------
end ------------
执行任务1
崩溃(死锁)

7.4 以下代码会不会发生死锁现象?为什么?

  • 不会,因为在子线程的串行队列中是异步执行任务
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"begin ------------");
    dispatch_queue_t queue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务1");
        dispatch_async(queue, ^{
            NSLog(@"执行任务2");
        });
        NSLog(@"执行任务3");
    });
    NSLog(@"end ------------");

}

总结:使用sync函数当前串行队列中添加任务,就会产生死锁。

八、队列组的使用(补充)

如何用GCD实现以下需求:

需求1:异步执行任务1和任务2,等任务1和任务2执行完毕后,回到主线程执行任务3

完成上面的需求,就需要使用队列组的技术

    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    // 任务1
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1 - %@", [NSThread currentThread]);
        }
    });
    
    // 任务2
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2 - %@", [NSThread currentThread]);
        }
    });
    
    // 任务3
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务3 - %@", [NSThread currentThread]);
        }
    });

// 打印结果:
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务2 - <NSThread: 0x6000036b6000>{number = 6, name = (null)}
任务1 - <NSThread: 0x6000036d9a40>{number = 3, name = (null)}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}
任务3 - <NSThread: 0x6000036ac100>{number = 1, name = main}

通过打印的结果发现,任务1和任务2异步执行,任务1 和任务2执行完毕后,在主线程执行任务3,通过使用dispatch_group_notify方法通知队列组在执行完任务1和任务2之后,自动执行任务3。

需求二:异步执行任务1和任务2,等任务1和任务2执行完毕后,异步执行任务3和任务4

    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    // 任务1
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务1 - %@", [NSThread currentThread]);
        }
    });
    
    // 任务2
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务2 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务3 - %@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"任务4 - %@", [NSThread currentThread]);
        }
    });

打印结果:
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务2 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务1 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}
任务3 - <NSThread: 0x600001fdcb00>{number = 3, name = (null)}
任务4 - <NSThread: 0x600001fdcac0>{number = 6, name = (null)}

从打印结果中可以发现,先异步执行任务1和任务2,等任务1 和任务2执行完毕后,异步执行任务3和任务4。通过dispatch_group_notify来进行通知处理。