iOS GCD详解

作者: i_belive | 来源:发表于2018-02-23 18:09 被阅读236次
    • 1、GCD简介

    全名:Grand Central Dispatch,它是苹果为多核的并行运算提出的解决方案,会合理利用CPU、自动管理线程的生命周期。使用时只需要在Block中写入需要执行的代码即可。使用非常灵活。


    • 2、基本概念

      • 2.1、串行与并发

        串行 :每次只有一个任务被执行
        并发:同一时间可以有多个任务被执行
      • 2.2、同步与异步

        同步:完成代码块后才返回,会阻塞当前线程
        异步:代码块完成前就立即放回,不会阻塞当前线程
      • 2.3、临界区

        一段代码不能被并发执行,两个线程不能同时执行这段代码,否则这段代码中的相关数据变得不可信。
      • 2.4、竞态条件

        基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,可导致无法预测的行为,不能通过代码检查立即发现。
      • 2.5、死锁

        多个东西因为互相等待而无法完成,导致它们都卡住了。
      • 2.6、线程安全

        代码能在多线程或并发任务中被安全的调用,而不会导致任何问题。
      • 2.7、上下文切换

        当你的单个进程里切换执行不同的线程是存储于恢复执行状态的过程

    • 3、队列

      • 主队列:用于刷新UI,任何需要刷新UI的工作都要放在主队列中执行,同时为防止UI卡住,一般需要把耗时的任务放在其他线程中执行

        dispatch_queue_t queue = dispatch_get_main_queue();
        
      • 全局队列:一般并行任务都加入这个队列中,这是系统提供的一个全局的并发队列

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        

        获取程序进程缺省产生的并发队列,可根据优先级来选择高、中、低三个优先级,
        由于这个是全局有系统控制的队列,所以我们无法对其进行 dispatch_resume() 继续 和 dispatch_suspend() 中断。

      • 自定义队列:可以自定义串行队列或者并行队列

        //串行队列
        dispatch_queue_t queue = dispatch_queue_create("demo", NULL);
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
        //并行队列
        dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
        

        第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空
        第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列


    • 4、GCD使用

      • 4.1、死锁问题:

        • 第一种:

          -(void)test1{
          NSLog(@"1");
          dispatch_sync(dispatch_get_main_queue(), ^{
             NSLog(@"2");
          });
          NSLog(@"3");
          } 
          

          输出:

          2018-02-23 16:12:36.353985+0800 GCDDemo[24815:1940432] 1
          

          会卡在dispatch_sync(dispatch_get_main_queue(), ^{,或者报异常
          由于是主队列同步执行而且block是后下入主队列的,所以block会放到主队列的后面等待主队列执行完毕后再执行,所以2是放在3的后面的。但是主线程也在等block执行完毕,这样主线程才会继续执行。也就是说3又在等2执行完毕才会执行。所以出现了死锁

        • 第二种:

          -(void)test2{
           NSLog(@"1");
           dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
               NSLog(@"2");
           });
           NSLog(@"3");
          }
          

          输出:

          2018-02-23 16:17:15.853493+0800 GCDDemo[24879:1950825] 1
          2018-02-23 16:17:15.853623+0800 GCDDemo[24879:1950825] 2
          2018-02-23 16:17:15.853715+0800 GCDDemo[24879:1950825] 3
          

          本次为同步全局队列。虽然主线程队列会等待2的执行,但是2这次没有放在3的后面而是在另一个全局队列中,所以不会造成死锁。

        • 第三种:

           -(void)test3{
               NSLog(@"1");
               dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                   NSLog(@"2");
                   dispatch_sync(dispatch_get_main_queue(), ^{
                       NSLog(@"3");
                   });
                   NSLog(@"4");
               });
               NSLog(@"5");
           }
          

          输出:

          2018-02-23 16:21:25.714923+0800 GCDDemo[24938:1961169] 1
          2018-02-23 16:21:25.715117+0800 GCDDemo[24938:1961169] 5
          2018-02-23 16:21:25.715124+0800 GCDDemo[24938:1961306] 2
          2018-02-23 16:21:25.718781+0800 GCDDemo[24938:1961169] 3
          2018-02-23 16:21:25.718926+0800 GCDDemo[24938:1961306] 4
          

          这里看似和第一种类似好像会造成死锁,其实不是的,这次主队列其实已经运行完

        • 第四种:

           -(void)test4{
               NSLog(@"1");
               dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                   NSLog(@"2");
                   dispatch_sync(dispatch_get_main_queue(), ^{
                       NSLog(@"3");
                   });
                   NSLog(@"4");
               });
               NSLog(@"5");
               while (YES) {
                   
               }
               NSLog(@"6");
           }
          

          输出:

          2018-02-23 16:26:49.901088+0800 GCDDemo[25017:1973298] 1
          2018-02-23 16:26:49.901234+0800 GCDDemo[25017:1973298] 5
          2018-02-23 16:26:49.901246+0800 GCDDemo[25017:1973398] 2
          

          主线程没有执行完,6没打印,所以3还是放在主线程队列后面,但是主线程没有执行完,不会执行3。而3是同步的,所以3不执行完4也不会打印

      • 4.2、常见GCD操作:

        • 并发同步:

            -(void)test7{
                //并发同步
                NSLog(@"start");
                dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"1----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"2----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"3----:%@",[NSThread currentThread]);
                    }
                });
                NSLog(@"end");
            }
          

          输出:

            2018-02-23 16:36:18.856563+0800 GCDDemo[25130:1996747] start
            2018-02-23 16:36:18.856797+0800 GCDDemo[25130:1996747] 1----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.856986+0800 GCDDemo[25130:1996747] 1----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.857095+0800 GCDDemo[25130:1996747] 2----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.857388+0800 GCDDemo[25130:1996747] 2----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.857588+0800 GCDDemo[25130:1996747] 3----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.857749+0800 GCDDemo[25130:1996747] 3----:<NSThread: 0x600000077040>{number = 1, name = main}
            2018-02-23 16:36:18.857875+0800 GCDDemo[25130:1996747] end
          
        • 并发异步:

            -(void)test8{
                //并发异步
                NSLog(@"start");
                dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_CONCURRENT);
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"1----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"2----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"3----:%@",[NSThread currentThread]);
                    }
                });
                NSLog(@"end");
            }
          

          输出:

            2018-02-23 16:38:26.222795+0800 GCDDemo[25170:2002606] start
            2018-02-23 16:38:26.222964+0800 GCDDemo[25170:2002606] end
            2018-02-23 16:38:26.223061+0800 GCDDemo[25170:2002744] 1----:<NSThread: 0x600000267700>{number = 3, name = (null)}
            2018-02-23 16:38:26.223092+0800 GCDDemo[25170:2002747] 2----:<NSThread: 0x600000267640>{number = 4, name = (null)}
            2018-02-23 16:38:26.223104+0800 GCDDemo[25170:2002745] 3----:<NSThread: 0x604000465b00>{number = 5, name = (null)}
            2018-02-23 16:38:26.223231+0800 GCDDemo[25170:2002744] 1----:<NSThread: 0x600000267700>{number = 3, name = (null)}
            2018-02-23 16:38:26.223389+0800 GCDDemo[25170:2002747] 2----:<NSThread: 0x600000267640>{number = 4, name = (null)}
            2018-02-23 16:38:26.223480+0800 GCDDemo[25170:2002745] 3----:<NSThread: 0x604000465b00>{number = 5, name = (null)}
          
          
        • 串行同步:

            -(void)test9{
                //串行同步
                NSLog(@"start");
                dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"1----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"2----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_sync(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"3----:%@",[NSThread currentThread]);
                    }
                });
                NSLog(@"end");
            }
          

          输出:

            2018-02-23 16:42:13.941307+0800 GCDDemo[25237:2012245] start
            2018-02-23 16:42:13.941525+0800 GCDDemo[25237:2012245] 1----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.941707+0800 GCDDemo[25237:2012245] 1----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.941940+0800 GCDDemo[25237:2012245] 2----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.942073+0800 GCDDemo[25237:2012245] 2----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.942181+0800 GCDDemo[25237:2012245] 3----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.942279+0800 GCDDemo[25237:2012245] 3----:<NSThread: 0x600000065680>{number = 1, name = main}
            2018-02-23 16:42:13.942372+0800 GCDDemo[25237:2012245] end
          
        • 串行异步:

            -(void)test10{
                //串行异步
                NSLog(@"start");
                dispatch_queue_t queue = dispatch_queue_create("demo", DISPATCH_QUEUE_SERIAL);
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"1----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"2----:%@",[NSThread currentThread]);
                    }
                });
                dispatch_async(queue, ^{
                    for (int i = 0; i < 2; i++) {
                        NSLog(@"3----:%@",[NSThread currentThread]);
                    }
                });
                NSLog(@"end");
            }
          

          输出:

            2018-02-23 16:43:05.320128+0800 GCDDemo[25263:2014958] start
            2018-02-23 16:43:05.320452+0800 GCDDemo[25263:2014958] end
            2018-02-23 16:43:05.320553+0800 GCDDemo[25263:2015026] 1----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
            2018-02-23 16:43:05.321290+0800 GCDDemo[25263:2015026] 1----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
            2018-02-23 16:43:05.322392+0800 GCDDemo[25263:2015026] 2----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
            2018-02-23 16:43:05.322896+0800 GCDDemo[25263:2015026] 2----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
            2018-02-23 16:43:05.323378+0800 GCDDemo[25263:2015026] 3----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
            2018-02-23 16:43:05.323637+0800 GCDDemo[25263:2015026] 3----:<NSThread: 0x600000079d80>{number = 3, name = (null)}
          
    • 5、GCD线程之间的通讯

      在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 2; ++i) {
                NSLog(@"1------%@",[NSThread currentThread]);
            }
      
            // 回到主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"2-------%@",[NSThread currentThread]);
            });
        });
      
    • 6、 GCD的其他方法

      • 6.1、GCD的栅栏方法 dispatch_barrier_async

        我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。

        - (void)barrier
        {
            dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
        
            dispatch_async(queue, ^{
                NSLog(@"----1-----%@", [NSThread currentThread]);
            });
            dispatch_async(queue, ^{
                NSLog(@"----2-----%@", [NSThread currentThread]);
            });
        
            dispatch_barrier_async(queue, ^{
                NSLog(@"----barrier-----%@", [NSThread currentThread]);
            });
        
            dispatch_async(queue, ^{
                NSLog(@"----3-----%@", [NSThread currentThread]);
            });
            dispatch_async(queue, ^{
                NSLog(@"----4-----%@", [NSThread currentThread]);
            });
        }
        

        可以用来处理 读者与写者问题

      • 6.2、GCD的延时执行方法 dispatch_after

        当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
          // 2秒后异步执行这里的代码...
         NSLog(@"run-----");
        });
        
      • 6.3、GCD的一次性代码(只执行一次) dispatch_once

        我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。

        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 只执行1次的代码(这里面默认是线程安全的)
        });
        
      • 6.4、GCD的快速迭代方法 dispatch_apply

        通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。比如说遍历0~5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_apply(6, queue, ^(size_t index) {
          NSLog(@"%zd------%@",index, [NSThread currentThread]);
        });
        
      • 6.5、GCD的队列组 dispatch_group

        有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。

        • 我们可以先把任务放到队列中,然后将队列放入队列组中
        • 调用队列组的dispatch_group_notify回到主线程执行操作
        dispatch_group_t group =  dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          // 执行1个耗时的操作
              NSLog(@"耗时操作");
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
          // 执行1个耗时的操作
              NSLog(@"耗时操作");
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
          // 等前面的异步操作都执行完毕后,回到主线程...
        });
        

        也可以使用dispatch_group_enterdispatch_group_leave来通知 Dispatch Group 任务开始和完成,同时需要注意dispatch_group_enterdispatch_group_leave是成对出现。

    • 7、 其他问题

      • 读者与写者问题

        在多线程中一个对象的数据被同时读取和写入将会导致数据不可信。例如,多线程可以同时读取NSMutableArray的一个实例而不会产生问题,但是当一个线程正在读取时,另一个线程正在修改数据就是不安全的。

        //写入
        -(void)addObj:(id)obj{
            if(obj){
              [_objArray addObject:obj];
            }
        }
        //读取
        -(NSArray *)array{
            return [NSArray arrayWithArray:_objArray];
        }
        

        这里所谓的方法,它读取可变数据。它为调用者生成一个不可变的拷贝,防止调用者不当的改变数组,但是这不能提供任何保护来对抗一个线程调用读方法的同时另一个线程调用写方法。
        GCD通过用dispatch barriers创建一个读写锁提供解决方案。
        Dispatch barriers 是一组函数,在并发队列上工作时扮演一个串行式的瓶颈。使用 GCD 的障碍(barrier)API 确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。这就意味着所有的先于调度障碍提交到队列的条目必能在这个 Block 执行前完成。
        当这个 Block 的时机到达,调度障碍执行这个 Block 并确保在那个时间里队列不会执行任何其它 Block 。一旦完成,队列就返回到它默认的实现状态。 GCD 提供了同步和异步两种障碍函数。
        下面是你何时会——和不会——使用障碍函数的情况:

        • 自定义串行队列:一个很坏的选择;障碍不会有任何帮助,因为不管怎样,一个串行队列一次都只执行一个操作。
        • 全局并发队列:要小心;这可能不是最好的主意,因为其它系统可能在使用队列而且你不能垄断它们只为你自己的目的。
        • 自定义并发队列:这对于原子或临界区代码来说是极佳的选择。任何你在设置或实例化的需要线程安全的事物都是使用障碍的最佳候选。

        由于上面唯一像样的选择是自定义并发队列,你将创建一个你自己的队列去处理你的障碍函数并分开读和写函数。且这个并发队列将允许多个多操作同时进行。

        @interface SomeObject ()
        @property (nonatomic,strong,readonly) NSMutableArray *objArray;
        @property (nonatomic, strong) dispatch_queue_t concurrentPhotoQueue; ///< Add this
        @end
        
            -(id)init{
              ...
              _concurrentPhotoQueue = dispatch_queue_create("demo",
                                                        DISPATCH_QUEUE_CONCURRENT);
            }
        

        注意此处使用的是并发队列。如果改为串行队列,只能是单读、单写操作,在效率上会有折扣。
        修改写入函数

        -(void)addObj:(id)obj
        {
            if (obj) { // 1
                dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2 
                    [_objArray addObject:obj]; // 3
                });
            }
        }
        

        新写的函数是这样工作的:

        1. 在执行下面所有的工作前检查是否有合法的对象。
        2. 添加写操作到你的自定义队列。当临界区在稍后执行时,这将是你队列中唯一执行的条目。
        3. 这是添加对象到数组的实际代码。由于它是一个障碍 Block ,这个 Block 永远不会同时和其它 Block 一起在 concurrentPhotoQueue 中执行。

        修改读取函数

        -(NSArray *)array
        {
            __block NSArray *retArray; // 1
            dispatch_sync(self.concurrentPhotoQueue, ^{ // 2
                retArray = [NSArray arrayWithArray:_objArray]; // 3
            });
            return retArray;
        }
        

        按顺序看看编过号的注释,有这些:

        1. __block 关键字允许对象在 Block 内可变。没有它,retArray 在 Block 内部就只是只读的,你的代码甚至不能通过编译。
        2. 在 concurrentPhotoQueue 上同步调度来执行读操作。
        3. 将数组存储在 retArray 内并返回它。

    相关文章

      网友评论

      本文标题:iOS GCD详解

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