美文网首页将来跳槽用
iOS-GCD多线程死锁案例

iOS-GCD多线程死锁案例

作者: 爱恨的潮汐 | 来源:发表于2019-06-10 18:08 被阅读8次

一、死锁场景: 主线程调用主线程。

- (void)deadLockCase1 {
    NSLog(@"1"); // 任务1
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任务2
    });
    NSLog(@"3"); // 任务3
}

控制台输出:1 ,后面就崩溃了。

原因:
从控制台输出可以看出,任务2和任务3没有执行,此时已经死锁了。
因为dispatch_sync是同步的,本身就会阻塞当前线程,此刻阻塞了主线程。而当前block又在等待主线程执行完毕,从而形成了主线程等待主线程,自己等自己的情况,形成了死锁。
解决方法:

1、改用异步dispatch_async执行
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2"); // 任务2
 });
NSLog(@"3"); // 任务3

控制台输出:1 3 2
2、不在主线程中运行,而是放在子线程中
NSLog(@"1"); // 任务1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3

控制台输出:1 2 3

注:如果block中是刷新UI的操作,则不能放在子线程中执行,会crash

二、死锁场景2: (同步串行队列嵌套自己)

- (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任务1
    dispatch_sync(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任务2
        dispatch_sync(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任务3
        });
        NSLog(@"4");  //任务4
    });
    NSLog(@"5");  //任务5
}
控制台输出:1 2 ,执行到2后面就崩溃了。

原因:
从控制台输出结果来看,执行到任务2后,就已经死锁了。因为该例子中两个GCD都是使用的同步方式,而且还是同一个串行队列,这就导致了和上一个例子一样,自己在等待自己的情况,形成了死锁。
解决方法:

1、将第二个GCD改为异步
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任务1
    dispatch_sync(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任务2
        dispatch_async(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任务3
        });
        NSLog(@"4");  //任务4
    });
    NSLog(@"5");  //任务5
}
控制台输出:1 2 4 5 3

然而,将第一个GCD改为异步,不能解决问题

 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任务1
    dispatch_async(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任务2
        dispatch_sync(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任务3
        });
        NSLog(@"4");  //任务4
    });
    NSLog(@"5");  //任务5
}

控制台输出:
1
5
2

原因:
虽然第一个GCD是异步的,但是第二个GCD是同步的,第二个GCD在等着第一个GCD结束,而第一个GCD的block又在等着第一个GCD结束,这样就形成了死锁。
注:对于以上将第二个GCD改为异步,第一个GCD为同步的场景,不会造成死锁,是因为第二个GCD为异步,它不用等待第一个GCD执行完毕,它和第一个GCD是没有同步关系的。它是在第一个GCD执行的同时并发执行自己block的代码。

2、将两个GCD都改为异步
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create("com.test.deadlock.queue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任务1
    dispatch_async(aSerialDispatchQueue, ^{
        NSLog(@"2"); //任务2
        dispatch_async(aSerialDispatchQueue, ^{
            NSLog(@"3"); //任务3
        });
        NSLog(@"4");  //任务4
    });
    NSLog(@"5");  //任务5
}

控制台输出:
1
5
2
4
3
3、使用不同的串行队列
 - (void)deadLockCase2 {
    dispatch_queue_t aSerialDispatchQueue1 = dispatch_queue_create("com.test.deadlock.queue1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t aSerialDispatchQueue2 = dispatch_queue_create("com.test.deadlock.queue2", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1"); //任务1
    dispatch_sync(aSerialDispatchQueue1, ^{
        NSLog(@"2"); //任务2
        dispatch_sync(aSerialDispatchQueue2, ^{
            NSLog(@"3"); //任务3
        });
        NSLog(@"4");  //任务4
    });
    NSLog(@"5");  //任务5
}

控制台输出:
1
2
3
4
5

三、 死锁场景: 信号量阻塞主线程

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    NSLog(@"semaphore create!");
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_semaphore_signal(semaphore);
        NSLog(@"semaphore plus 1");
    });
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore minus 1");
}

原因:
如果当前执行的线程是主线程,以上代码就会出现死锁。
因为dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)阻塞了当前线程,而且等待时间是DISPATCH_TIME_FOREVER——永远等待,这样它就永远的阻塞了当前线程——主线程。导致主线中的dispatch_semaphore_signal(semaphore)没有执行,
而dispatch_semaphore_wait一直在等待dispatch_semaphore_signal改变信号量,这样就形成了死锁。
解决方法:
应该将信号量移到并行队列中,如全局调度队列。以下场景,移到串行队列也是可以的。但是串行队列还是有可能死锁的(如果执行dispatch_semaphore_signal方法还是在对应串行队列中的话,即之前提到的串行队列嵌套串行队列的场景)。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        NSLog(@"semaphore create!");
        dispatch_async(dispatch_get_main_queue(), ^{
            dispatch_semaphore_signal(semaphore);
            NSLog(@"semaphore plus 1");
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"semaphore minus 1");
    });
}

上面从上到下顺序输出

以下代码运行结果如何?

-(void)viewDidLoad {
    
    [super viewDidLoad];
    
    NSLog(@"1");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        
        NSLog(@"2");
    });
    
    NSLog(@"3");
}

只输出:1。后面就崩溃了(主线程死锁,因为viewDidLoad方法默认开了一条主线程,然后又执行dispatch_sync(dispatch_get_main_queue(), ^{...});会导致你等我我等你,结果导致死锁。

参考

相关文章

  • iOS-GCD多线程死锁案例

    一、死锁场景: 主线程调用主线程。 原因:从控制台输出可以看出,任务2和任务3没有执行,此时已经死锁了。因为dis...

  • Java并发案例01---多线程之死锁

    多线程之死锁案例一

  • jstack命令:教你如何排查多线程问题

    这是之前的一个死锁案例: 一个多线程死锁案例,如何避免及解决死锁问题? 如程序中发生这样的死锁问题该如何排查呢?我...

  • iOS-知识点

    五个案例让你明白GCD死锁:http://ios.jobbole.com/82622/ iOS开发中多线程间关于锁...

  • Java自学-多线程 死锁

    Java 演示多线程死锁 当业务比较复杂,多线程应用里有可能会发生死锁 步骤 1 : 演示死锁 线程1 首先占有对...

  • ORA-00060死锁案例

    以下只是测试简单的几个死锁案例,发现死锁不一定是数据产生,还有索引产生的死锁。延伸出来相关的死锁案例还是有很多的 ...

  • 并发编程情况下几个相应问题简介

    1.并发编程的挑战之死锁 ​ 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多...

  • 5. 死锁

    线程死锁 死锁是两个或更多线程阻塞着等待其它处于死锁状态的线程所持有的锁。死锁通常发生在多个线程同时但以不同的顺序...

  • 多线程分析——死锁代码demo

    前言 本文主要是对Java多线程死锁代码demo的分析,对其过程以及死锁进行了大致的概括,希望对大家有帮助。 死锁...

  • 面试官:连多线程问题你都一问三不知,还要我怎么“放水”?

    面试官:问你几个多线程相关的问题吧,说一下导致线程死锁的原因,怎么解除线程死锁? 程序员阿里:这个...死锁......

网友评论

    本文标题:iOS-GCD多线程死锁案例

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