美文网首页
iOS主线程死锁问题探究

iOS主线程死锁问题探究

作者: 萝卜酱紫 | 来源:发表于2020-07-08 00:08 被阅读0次

    主线程死锁很多人的解释不太一致,我自己也是多次理解之后明白大致的原因,因为最近面试有个面试官问了这个问题还写了如下代码,有自己的一套说法.再回去会又认真的研究了一下. 你猜对了,面试官不一定说的对.哈哈哈.总感觉中国特色面试,嘤嘤嘤

    面试官手写的代码:

    • (void)viewDidLoad {
      [super viewDidLoad];

      NSLog(@"1");

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

      NSLog(@"3");

      dispatch_sync( ,^{
      NSLog(@"%@",[NSThread currentThread]);
      NSLog(@"4");
      });

      NSLog(@"5");
      }

    打印结果:
    1
    3
    2

    然后程序崩溃 Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

    面试官当时在 dispatch_sync 和 dispatch_async 后面没有写队列,还说这俩方法穿不同队列会怎样怎样

    我当时的解释是: 主队列会有三个任务 viewDidLoad
    dispatch_async dispatch_sync 这三个任务ABC
    主队列是个串行队列 主线程会先执行viewDidLoad 走到dispatch_async的时候因为是异步的所以不会立即执行但会正常执行但是先打印2 还是3 我不确定,跟CPU调度有关系,但是走到dispatch_sync的时候是同步的,就告诉主线程立即执行带任务C,但是任务A包含了任务C 任务C执行需要主线程是空闲的,但是A任务执行完需要C任务执行完,C任务执行完得等到A任务执行完,所以相互等待,主线程死锁

    以下是我后来的多次代码验证 (请认真看打印结果)

    //全局队列:(并发队列)
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //串行队列
    dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);

    //并发队列
    dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);

    //主队列(串行发队列)
    dispatch_queue_t main = dispatch_get_main_queue();

    NSLog(@"1");
    
    dispatch_async(main, ^{
        NSLog(@"2");
    });
    
    NSLog(@"3");
    
    dispatch_sync(main, ^{
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"4");
    });
    
    NSLog(@"5");
    
    1. dispatch_async 这里放任何队列跟下面 dispatch_sync放什么队列没关系

    2. (1) dispatch_sync(main,^{...}) 注释的情况下:多次打印结果 1235 1325 1352都有出现,我猜测是CPU的调度导致的 (sige)
      (2)不注释情况下 执行结果 132 然后死锁
      (3)只会当 dispatch_sync(main) 会出现死锁

    后来我写了下面的代码:

    • (void)viewDidLoad {
      [super viewDidLoad]

      [self queue];

      int i = 10;
      while (i>0) {
      i-=1;
      NSLog(@"做事情");
      }

    }

    -(void)queue{
    //串行队列
    dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);

    //主队列(串行发队列)
    dispatch_queue_t main = dispatch_get_main_queue();

    //这里切换队列
    dispatch_queue_t queue = serial;

    NSLog(@"BEGIN");
    

    // 这里 dispatch_async dispatch_sync (不影响下面的结论)

    dispatch_async(queue, ^{
    

    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"BEGIN");
    [self performSelector:@selector(syncQueue:) onThread:[NSThread currentThread] withObject:queue waitUntilDone:YES];
    //waitUntilDone 意思是是否等test方法执行完才去执行下面(感兴趣的可以试试看看效果,你会诧异)

        NSLog(@"内部END");
        NSLog(@"%@",[NSThread currentThread]);
    });
    
    NSLog(@"外部END");
    

    }

    -( void)syncQueue:(dispatch_queue_t)queue{

    NSLog(@"queue = %@",queue);
    NSLog(@"1");
    NSLog(@"%@",[NSThread currentThread]);
    
    dispatch_sync(queue, ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        NSLog(@"2");
        
    });
    
    NSLog(@"3");
    

    }

    1. dispatch_queue_t queue = main 的时候
      (1)NSLog(@"BEGIN"); 下面是dispatch_sync 就会直接死锁
      (2)NSLog(@"BEGIN"); 下面是dispatch_async 会在
      syncQueue方法哪里的sync 死锁

    第一种死锁: 主队列的任务A 在主线程执行过程中,又要去同步执行主队列的任务B

    1. dispatch_queue_t queue = serial 的时候

    NSLog(@"BEGIN"); 下面不管是是dispatch_async 还是dispatch_sync 都是在syncQueue方法哪里的dispatch_sync 死锁

     (1)NSLog(@"BEGIN");  下面是dispatch_sync  (queue) 不会开辟新的线程,任务是在主线程执行的,会在syncQueue方法哪里的sync 死锁
    

    原因:一个串行队列的任务A在主线程(串行队列)同步执行过程中,又要去同步执行该对列上的B任务,

     (2)NSLog(@"BEGIN");  下面是dispatch_async (queue) 会开辟新的线程,  会在syncQueue方法哪里的sync 死锁
    

    原因:一个串行队列的任务A在子线程异步执行(但是这个串行队列只有任务A,异步执行会直接执行)过程中,又要去同步执行主队列的任务B

    第二种死锁: 串行队列的任务A 在主线程或子线程执行过程中 又要去同步执行串行队列的任务B

    结论1:一个线程去串行队列空闲执行同步任务不会死锁,不空闲执行同步任务会死锁

    如果你这样写:

    -(void)test{
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    //这里切换队列
    dispatch_queue_t queue = concurrent;
    
    NSLog(@"BEGIN");
    
    dispatch_async(queue, ^{
    

    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"BEGIN");
    [self performSelector:@selector(syncQueue:) onThread:[NSThread currentThread] withObject: queue waitUntilDone:YES];
    NSLog(@"内部END");
    NSLog(@"%@",[NSThread currentThread]);
    });

    NSLog(@"外部END");
    

    }

    -(void)syncQueue:(dispatch_queue_t)queue{

    NSLog(@"1");
    NSLog(@"%@",[NSThread currentThread]);
    
    //直接同步 主队列或者串行队列
    dispatch_sync(dispatch_get_main_queue(), ^{
        
        NSLog(@"%@",[NSThread currentThread]);
        
        NSLog(@"2");
        
    });
    
    NSLog(@"3");
    

    }

    dispatch_sync(dispatch_get_main_queue(), 这里不会死锁,是不是很纳闷.这个主队列同步任务没有死锁,是在主线程打印10次"做事情"之后执行的

    因为 dispatch_async(queue) 异步并发队列会创建新的线程, 你是通过子线程执行并发队列的异步任务过程中添加同步任务到主队列的(主线程执行任务A过程中并没有一个同步任务B,当A执行完的时候发现有个同步任务B),而不是在主线程执行任务过程中添加同步任务到主队列(主线程执行任务A过程中要去同步执行任务B),就是结论1的道理

    而如果 NSLog(@"BEGIN"); 下面是 dispatch_sync(queue) 会在syncQueue: 方法 dispatch_sync(dispatch_get_main_queue()这里死锁,

    因为同步并发队列不会创建新的线程(只有异步才能开辟新的线程,并发队列才具备异步执行任务的能力,并发队列异步执行任务,任务才会无序的执行)
    syncQueue方法是在主线程执行的 然后你又dispatch_sync(dispatch_get_main_queue() 当然死锁 (主线程执行主队列上的任务A过程中A又要去同步执行任务B)

    个人观点:
    线程死锁会出现在一个线程执行一个队列的任务过程中(任务A没执行完)又要去执行同步任务B

    这里也就明白了为什么同步不能创建新的线程:

    在一个线程上同步执行一个队列上的任务,如果创建新的线程去执行该任务,CPU调度不同的线程,那个线程上的任务无法保证有序的执行完任务,同步的操作就没意义了

    相关文章

      网友评论

          本文标题:iOS主线程死锁问题探究

          本文链接:https://www.haomeiwen.com/subject/rrftcktx.html