IOS面试题(多线程) --- GCD

OC面试题目合集地址

问题1: GCD调用的四种组合/ 讲一下你对GCD的了解

  • 同步串行: 同步分配任务到串行队列
dispatch_sync(serial_queue, ^{ 任务 })
  • 异步串行: 异步分配任务到串行队列
dispatch_async(serial_queue, ^{ 任务 })
  • 同步并行: 同步分配任务到并发队列
dispatch_sync(concurrent_queue, ^{ 任务 })
  • 异步并行: 异步分配任务到并发队列
dispatch_async(concurrent_queue, ^{ 任务 })

同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)

同步函数 dispatch_sync

  • 串行队列: 不会开启线程
  • 并发队列: 不会开启线程

异步函数 dispatch_async

  • 串行队列: 1条线程
  • 并发队列: N条线程

留意下, 异步有不新开线程情况

任务追加线程已存在,比如 main 线程,则不会开线程,而是使用线程。例如: 在串行队列Queue任务A里异步将任务B追加到队列Queue中,此时任务B任务A在一个线程,不开线程。



问题2: GCD自定义queue有几种类型

serial串行

又称为private dispatch queues,同一时刻只执行一个任务,并按添加到serial的顺序执行。当创建多个serial queue时,虽然它们各自是同步执行的,但serial queueserial queue之间是并发执行的。serial queue通常用于同步访问特定的资源或数据。

concurrent并行

又称为global dispatch queue,同一时刻可执行多个任务,任务开始执行的顺序按添加的顺序执行,但是执行完成的顺序是随机的,同时可以创建执行的任务数量依赖系统条件。

Main dispatch queue 主队列

全局可用的serial queue,它是在应用程序主线程上执行任务的。



问题3: 下面代码运行结果会什么样?

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSLog(@"1");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        
        NSLog(@"2");
        
    });
    
    NSLog(@"3");
}

考察同步串行问题

答案:

1 然后 Crash

其实因为这里的dispatch_sync是个死锁

    // 死锁
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
  • 死锁原因: 由队列引起的循环等待 (留意下并不是由线程引起的循环等待)

这段代码的进行的逻辑为


问题3逻辑
  • 我们首先在主队列里面提交了一个viewDidLoad任务, 接下来又提交了一个Block任务。当然这两个任务都被分配到主线程中去执行。

  • 现在我们分配viewDidLoad到主线程中去处理, 因为viewDidLoad中有dispatch_get_main_queue 的 Block, 那么只有Block调用之后viewDidLoad才能往下走。

  • 而主队列有个特点: 先进先出 FIFO, 即block执行完成必须要等待viewDidLoad结束(完成)才可以。这样就形成了一个相互等待的场面 --- 死锁

验证:

问题3结果


问题4: 下面代码运行结果会什么样?

    NSLog(@"1");

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{

        NSLog(@"2");

    });

    NSLog(@"3");

跟问题3不同, 这端代码涉及2个队列, 主队列和自定义串行队列queue

问题4图解
  • 主队列提交一个viewdidload方法, 当然这个方法在主线程里面执行
  • viewdidload执行中, 需要同步提交一个任务到自定义串行队列上面, 同步提交需要在相同线程执行, 固也在主线程执行
  • 串行队列在主线程执行任务之后, 再去继续执行主队列后续代码逻辑

所以正常运行

答案:

正常运行 1 2 3

问题4答案

当然如果创建的队列是主队列, 如下

    // dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue = dispatch_get_main_queue();

那么问题4例子还是跟问题3一样, 死锁 crash



问题5: 下面代码运行结果会什么样?

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    NSLog(@"1");
    
    dispatch_sync(queue, ^{
        
        NSLog(@"2");
        
        dispatch_sync(queue, ^{
            
            NSLog(@"3");
            
        });
        
        NSLog(@"4");
        
    });
    
    NSLog(@"5");
  
}

  • 同步提交任务无论串行或者并发都是在当前线程中执行。上面例子在viewDidLoad里面执行, 即主线程里面执行。
  • 先执行NSLog(@"1")然后走同步并发队列dispatch_sync(queue, ^{, 里面的NSLog(@"2")
  • 里层的dispatch_sync(queue, ^{,也是个串行并发队列, 在当前线程执行即主线程
  • 同时由于并发队列特点, 我们提交到这个队列里面的block可以并发执行, 那么继续执行NSLog(@"3")
  • 之后继续执行NSLog(@"4"), NSLog(@"5")

答案:

正常运行 1 2 3 4 5

问题5结果


问题6: 下面代码运行结果会什么样?

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_queue_create("test1", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"1");

    dispatch_sync(queue, ^{

        NSLog(@"2");

        dispatch_sync(queue, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");

}

其实这个道题跟问题3 道理相同

原因都是: 由队列引起的循环等待造成 死锁

  • 同步提交任务无论串行或者并发都是在当前线程中执行。上面例子在viewDidLoad里面执行, 即主线程里面执行。

  • 里层外层的串行队列都是相同线程, 即主线程中执行。由于是同步方式外层dispatch_sync结束需要等待里层dispatch_sync结束, 而里层dispatch_sync结束有需要等待外层dispatch_sync结束, 这样形成循环等待导致死锁

答案:

异常运行 1 2 Crash

问题6结果


问题7: 下面代码运行结果会什么样?

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"1");

    dispatch_async(dispatch_get_main_queue(), ^{
        
        NSLog(@"2");
        
    });
}

异步分配到串行队列, 这里是主队列。viewDidLoad执行结束之后, 执行异步任务dispatch_async

答案:

运行正常 1 2

问题7结果


问题8: 下面代码运行结果会什么样?

- (void)viewDidLoad {
    [super viewDidLoad];

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
   
    dispatch_async(queue, ^{
        
        NSLog(@"1");
        
        [self performSelector:@selector(testAction1)];
        [self performSelector:@selector(testAction2) withObject:nil afterDelay:0];
         
        NSLog(@"4");
        
    });
    
    
}

- (void)testAction1 {
    
    NSLog(@"2");
    
}


- (void)testAction2 {
    
    NSLog(@"3");
    
}

  • 异步到一个全局队列, 先执行NSLog(@"1");

  • 执行performSelector :方法, 由于performSelector是NSObject上的对象方法, 正常执行

NSObject方法
  • performSelector: withObject: afterDelay:runloop方法, 要提交到runloop上执行, 而GCD底层不会创建runloop的, 所以失效不会执行, 往下走
NSRunLoop方法
  • 1打印完打印2, 不会执行performSelector: withObject: afterDelay: , 接下来打印4, 所以结果1, 2, 4

答案:

运行正常 1 2 4

问题8结果


问题9: 怎样用GCD实现多读单写

例子

例如内存中维护一个字典, 有多个读者和写者操作这块数据

  • 读者读者并发, 可以同一时间可以多个读者读取数据, 相互不受影响
  • 读者写者互斥, 有读者在读取数据, 就不能有写的线程在处理
  • 写者写者互斥, 有写者在写数据, 其他就不能写数据, 防止数据错乱

所以我们设计的模式就是


设计模式

当做写处理的时候通过一个"栅栏"来挡住读处理, 等写处理完成之后再进行读处理

GCD 栅栏函数

dispatch_barrier_async(异步队列, ^{ // 写操作 } );

看个例子, 有三组并发队列, 队列A读取, 队列B更改数据, 队列C读取更改完的数据

如果我们不做任何操作, 如下

@interface ViewController ()

@property(nonatomic, assign) int a;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
 
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    
    dispatch_async(queue, ^{
        NSLog(@"队列A读取: %d", self.a);
    });
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(queue, ^{
        
        for (int i = 0; i < 10; i++) {
            weakSelf.a = weakSelf.a + 1;
            
            NSLog(@"队列B数据变更: %d", self.a);
        }
    });
    
    dispatch_async(queue, ^{
        NSLog(@"队列C读取: %d", self.a);
    });
}

错误打印结果


错误示范

很明显并未达到预期, 那么我们加一个栅栏, 改成

    dispatch_async(queue, ^{
        NSLog(@"队列A读取: %d", self.a);
    });
    
    __weak typeof(self) weakSelf = self;
    
//    dispatch_async(queue, ^{
//
//        for (int i = 0; i < 10; i++) {
//            weakSelf.a = weakSelf.a + 1;
//
//            NSLog(@"队列B数据变更: %d", self.a);
//        }
//    });
    
    dispatch_barrier_async(queue, ^{
        
        for (int i = 0; i < 10; i++) {
            weakSelf.a = weakSelf.a + 1;
            
            NSLog(@"栅栏: %d", self.a);
        }
    });
    
    dispatch_async(queue, ^{
        NSLog(@"队列C读取: %d", self.a);
    });
正确结果

可看出这样满足我们的条件



问题10: viewDidLoad中有3组异步并发队列, 每个队列顺序打印10个数, 实现1~30打印

其实这道题也是考察栅栏方法dispatch_barrier_async, 建议先看问题9

如果我们正常写3个并发队列

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //打印1到10
        for (int i = 1; i < 11; i++) {
            NSLog(@"0到10: %d",i);
        }
    });
    
    dispatch_async(queue, ^{
        //打印11到20
        for (int i = 11; i < 21; i++) {
            NSLog(@"11到20: %d",i);
        }
    });
  
    dispatch_async(queue, ^{
        //打印21到30
        for (int i = 21; i < 31; i++) {
            NSLog(@"21到30: %d",i);
        }
    });

打印结果

错误示范

那么这个时候dispatch_barrier_async就可以起到这个作用

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //打印1到10
        for (int i = 1; i < 11; i++) {
            NSLog(@"0到10: %d",i);
        }
    });
    
//    dispatch_async(queue, ^{
//        //打印11到20
//        for (int i = 11; i < 21; i++) {
//            NSLog(@"11到20: %d",i);
//        }
//    });
  
    dispatch_barrier_async(queue, ^{
        //打印11到20
        for (int i = 11; i < 21; i++) {
            NSLog(@"11到20: %d",i);
        }
    });
    
    dispatch_async(queue, ^{
        //打印21到30
        for (int i = 21; i < 31; i++) {
            NSLog(@"21到30: %d",i);
        }
    });
    
正确示范


问题11: 使用GCD实现: A, B, C 三个任务并发, 完成后执行任务D

其实这道题就是考察 dispatch_group_async的使用

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    NSMutableArray *arr = [NSMutableArray array];
    
    dispatch_group_t group = dispatch_group_create();
    
    for (int i = 0; i < 3; i++) {
        
        dispatch_group_async(group, queue, ^{
           
            NSLog(@"执行: %d 当前数组: %@", i, arr);
            [arr addObject:@(i)];
            
        });

    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部执行完: %@", arr);
    });
   
}
运行结果

你可能感兴趣的:(IOS面试题(多线程) --- GCD)