zoukankan      html  css  js  c++  java
  • iOS多线程开发之GCD(死锁篇)

         上篇和中篇讲解了什么是GCD,如何使用GCD,这篇文章将讲解使用GCD中将遇到的死锁问题。有兴趣的朋友可以回顾《iOS多线程开发之GCD(上篇)》和《iOS多线程开发之GCD(中篇)》。

         言归正传,我们首先来回顾下死锁,所谓死锁: 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源(如数据源,内存等,变量不是资源)而造成的一种互相等待的现象,若无外部处理作用,它们都将无限等待下去。

      死锁形成的原因:

    1. 系统资源不足
    2. 进程(线程)推进的顺序不恰当;
    3. 资源分配不当

      死锁形成的条件:

    1. 互斥条件:所谓互斥就是进程在某一时间内独占资源。
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

         

          在GCD中,主要的死锁就是当前串行队列里面同步执行当前串行队列。解决的方法就是将同步的串行队列放到另外一个线程执行。在举例说明之前,我们先来回顾下GCD的中的任务派发和队列。

        (1)任务派发

    任务派发方式说明
    dispatch_sync() 同步执行,完成了它预定的任务后才返回,阻塞当前线程
    dispatch_async() 异步执行,会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程

        (2)队列种类

    队列种类说明
    串行队列 每次只能执行一个任务,并且必须等待前一个执行任务完成
    并发队列 一次可以并发执行多个任务,不必等待执行中的任务完成

         (3)GCD队列种类

    GCD队列种类获取方法队列类型说明
    主队列 dispatch_get_main_queue 串行队列 主线中执行
    全局队列 dispatch_get_global_queue 并发队列 子线程中执行
    用户队列 dispatch_queue_create 串并都可以 子线程中执行

           由此我们可以得出:串行与并行针对的是队列,而同步与异步,针对的则是线程!

           

         案例分析:

            一、同步执行遇到串行队列

    - (void)syncMain{
        
        dispatch_queue_t queue = dispatch_get_main_queue();
        
        NSLog(@"task1-%@",[NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task3-%@",[NSThread currentThread]);
    }

           打印结果:

    2017-07-10 17:54:43.623 beck.wang[1405:182548] task1-<NSThread: 0x608000066000>{number = 1, name = main}

          分析:死锁

          原因:从打印结果可以看出,task1是在主线程中执行,而主线程是串行队列,定义的queue队列也是主队列, dispatch_sync是同步执行的标志,意思是必须等待block返回,才能执行task3,而当前主队列中正在被task1执行,必须等待完成task3完成后才能释放,这就造成了task3等待block完成返回,block等待task3完成释放主队列而相互等待的循环中死锁。

          扩展在主线程使用sync函数就会造成死锁”或者“在主线程使用sync函数,同时传入串行队列就会死锁”吗? NO,这种说明明显是没有真正理解死锁!从上面的案例中我们很明显的知道,死锁产生的原因是队列的阻塞。那么如果我自定义一个串行队列,不与主队列争宠呢?

    - (void)syncMain{
        
        // 注意这里的queue是自定义的串行队列,而不是主队列dispatch_get_main_queue()
        dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
        
        NSLog(@"task1-%@",[NSThread currentThread]);
        
        dispatch_sync(queue, ^{
            
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task3-%@",[NSThread currentThread]);
    }

           打印结果:

    2017-07-10 18:07:15.134 beck.wang[1427:192164] task1-<NSThread: 0x600000074800>{number = 1, name = main}
    2017-07-10 18:07:15.135 beck.wang[1427:192164] task2-<NSThread: 0x600000074800>{number = 1, name = main}
    2017-07-10 18:07:15.135 beck.wang[1427:192164] task3-<NSThread: 0x600000074800>{number = 1, name = main}

          分析:不开启新线程,顺序执行。

          原因:task1、task3与task2执行的队列不一样,不会阻塞。

          二、同步执行遇到并行队列

    - (void)syncConcurrent{
        
        NSLog(@"task11-%@",[NSThread currentThread]);
        
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"task12-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task13-%@",[NSThread currentThread]);
    }

         打印结果:

    2017-07-10 18:15:11.957 beck.wang[1452:198567] task11-<NSThread: 0x608000071f00>{number = 1, name = main}
    2017-07-10 18:15:11.957 beck.wang[1452:198567] task12-<NSThread: 0x608000071f00>{number = 1, name = main}
    2017-07-10 18:15:11.957 beck.wang[1452:198567] task13-<NSThread: 0x608000071f00>{number = 1, name = main}

         分析:不开启新线程,顺序执行。

         原因:task1、task3与task2执行的队列不一样,不会阻塞。

         

         三、异步&同步组合

    - (void)gcdTest{
        
        dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
        
        NSLog(@"task1-%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
        
            NSLog(@"task2-%@",[NSThread currentThread]);
            
            dispatch_sync(queue, ^{
                NSLog(@"task3-%@",[NSThread currentThread]);
            });
            
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task5-%@",[NSThread currentThread]);
    }

         打印结果:

    2017-07-10 18:29:23.976 beck.wang[1562:207413] task1-<NSThread: 0x608000063400>{number = 1, name = main}
    2017-07-10 18:29:23.976 beck.wang[1562:207413] task5-<NSThread: 0x608000063400>{number = 1, name = main}
    2017-07-10 18:29:23.976 beck.wang[1562:207460] task2-<NSThread: 0x608000067940>{number = 3, name = (null)}

         分析:死锁

         原因:task2、task4与task3在同一队列中执行,dispatch_sync确定了task4需要等待task3完成后返回才能执行,而task2任务执行的时候已经占用了当前队列,需要等到task4完成后才能释放,这就造成了task3等待task4完成,task4等待task3返回的相互等待,这也是队列阻塞造成的死锁。

         扩展如果queue换成自定义并发队列或者dispatch_sync追加到非当前队列(如主队列),则不会发生死锁

    - (void)gcdTest{
        
        dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
        
        NSLog(@"task1-%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
        
            NSLog(@"task2-%@",[NSThread currentThread]);
            
            // 这里使用主队列,而非自定义的串行队列,则不会发生死锁,同理并行队列也不会死锁
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"task3-%@",[NSThread currentThread]);
            });
            
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task5-%@",[NSThread currentThread]);
    }

         打印结果:

    2017-07-10 18:38:20.214 beck.wang[1582:215721] task1-<NSThread: 0x608000069780>{number = 1, name = main}
    2017-07-10 18:38:20.214 beck.wang[1582:215721] task5-<NSThread: 0x608000069780>{number = 1, name = main}
    2017-07-10 18:38:20.214 beck.wang[1582:215779] task2-<NSThread: 0x618000069cc0>{number = 3, name = (null)}
    2017-07-10 18:38:20.217 beck.wang[1582:215721] task3-<NSThread: 0x608000069780>{number = 1, name = main}
    2017-07-10 18:38:20.217 beck.wang[1582:215779] task4-<NSThread: 0x618000069cc0>{number = 3, name = (null)}

        四、上面的扩展案例中,主线程阻塞。

    - (void)gcdTest{
        
        dispatch_queue_t queue = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL);
        
        NSLog(@"task1-%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
        
            NSLog(@"task2-%@",[NSThread currentThread]);
            
            // 这里虽然使用主队列,但主队列已经阻塞,后续代码失效
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"task3-%@",[NSThread currentThread]);
            });
            
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        
        NSLog(@"task5-%@",[NSThread currentThread]);
        
        while (1) {
            // 进入while的恒等循环,主线程(主队列)阻塞
        }
        
        NSLog(@"task6-%@",[NSThread currentThread]);
    }

           打印结果:

    2017-07-10 18:47:22.844 beck.wang[1657:223045] task1-<NSThread: 0x60000007afc0>{number = 1, name = main}
    2017-07-10 18:47:22.844 beck.wang[1657:223045] task5-<NSThread: 0x60000007afc0>{number = 1, name = main}
    2017-07-10 18:47:22.844 beck.wang[1657:223110] task2-<NSThread: 0x610000262700>{number = 3, name = (null)}

         分析:主线程进入无限阻塞状态task6、task3、task4都无法访问到,处于无限等待状态。

         PS:这篇文章有借鉴部分,我写这篇博文的目的也是为了更好的理解GCD的死锁,毕竟好记性不如烂笔头嘛!在工作中我也会不断完成遇到的GCD的死锁情况,SO,本篇文章未完待续.....

  • 相关阅读:
    【java】对象赋值给另一个对象
    spring boot系列(五)spring boot 配置spring data jpa (查询方法)
    Spring Data JPA 查询
    Spring Data JPA 介绍
    OpenID简介
    OAUTH协议介绍
    URL encoding(URL编码)
    RESTful 介绍
    spring boot系列(四)spring boot 配置spring data jpa (保存修改删除方法)
    spring boot 启动报 java.lang.NoClassDefFoundError: ch/qos/logback/core/spi/LifeCycle 错误
  • 原文地址:https://www.cnblogs.com/beckwang0912/p/7146833.html
Copyright © 2011-2022 走看看