美文网首页
iOS多线程之GCD使用

iOS多线程之GCD使用

作者: nemie | 来源:发表于2017-09-12 23:58 被阅读31次

    一.GCD简介

    GCD(Grand Central Dispatch) 是iOS系统一套多线程操作API。字面意思既是宏观全局派发。相比于其他多线程API,GCD操作简单,功能强大,且会自动充分利用系统硬件资源。操作简单是因为我们无需管理线程,只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度任务,由系统直接提供线程管理。功能强大是指系统封装好了很多C函数,调用这些C函数就可以实现线程操作中的绝大多数功能。
    GCD里很重要的一个概念是队列queue,队列是一种里面的元素按先进先出规则的线性结构。我们可以把任务(往往都是block)往队列里添加,系统会自动去队里里取出任务,然后根据当前的队列是并行队列 还是串行 队列,响应的去分配到线程执行。
    队列相当于我们与系统交互的媒介,我们把任务往队列里扔,系统去队列里取出任务,开辟线程,分配任务。队列有并行队列与串行队列之分,不同队列有不同表现形式。


    二.并行串行、同步异步介绍

    学习GCD,首先要明白两组概念,并行和串行、同步与异步
    并行和串行比较好理解,按字面意思,并行指的并发执行,也就是开辟多个线程同时去执行队列里任务,串行的表现形式就是添加到队列里的任务会同时执行,因此后添加进队列的任务可能会先完成,而先添加进队列的任务不一定就先完成,串行则是指队里中的任务严格按照添加进队列的顺序先后执行。
    同步异步主要是指是否阻塞当前代码执行, 等待队列里的任务都执行完才可以继续执行。同步执行会先执行队列里的任务,执行完后等待dispatch_sync函数返回才能继续执行下面操作,异步执行不需要等待队列任务执行完成,将任务添加到队列后,dispatch_sync函数马上返回,继续执行下面操作,同时另外开辟新线程去执行队列里的任务。同步执行时候 系统会尽量做到不开辟新线程,只在当前线程里执行队列中的任务。

    三.GCD使用

    使用GCD很简单,两步即可完成,
    1.首先创建队列,
    2.然后将任务放到队列里。

    1.创建队列

    创建队列 可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于唯一标志队列,可以为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。默认NULL表示串行队列。

    // 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); 
    // 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT
    

    系统为了方便开发使用,已经定义好了一个全局并发队列,dispatch_get_global_queue,创建时候有两个参数,第一个参数表示队列优先级,一般用默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数由系统预留做以后使用,现在没用,可以先用0表示。

    2.向队列里分配任务,任务以block的形式被添加到队列中

    使用同步或者异步两种方式分配任务

    // 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });
    // 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });
    

    其中,任务既可以是临时声明的block,也可以是提前定义好的block。
    这个block是dispatch_async和dispatch_sync的参数

    @interface ViewController ()
    
    @property (nonatomic, copy) dispatch_block_t block;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //定义队列里需要执行的block,即任务
        _block = ^(){
        
            for (int i = 0; i < 10 ; i++) {
                
                NSLog(@"%d-------%@",i,[NSThread currentThread]);
            }
            
        };
        
        //创建队列  并行队列
        dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
        
        //异步执行创建好的队列
        dispatch_async(queue,_block);
    
        NSLog(@"等不等我");
    }
    

    运行结果如下

    2017-09-13 00:12:13.995 GCD[1148:97167] 等不等我
    2017-09-13 00:12:13.995 GCD[1148:97384] 0-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.997 GCD[1148:97384] 1-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.998 GCD[1148:97384] 2-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.998 GCD[1148:97384] 3-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.999 GCD[1148:97384] 4-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.999 GCD[1148:97384] 5-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:13.999 GCD[1148:97384] 6-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:14.000 GCD[1148:97384] 7-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:14.000 GCD[1148:97384] 8-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    2017-09-13 00:12:14.003 GCD[1148:97384] 9-------<NSThread: 0x600000265400>{number = 3, name = (null)}
    

    可以看到使用符合dispatch_block_t 形式的block都可以往队列里去添加,而dispatch_block_t的类型如下图所示

    dispatch_block_t类型.png

    可以看到,它就是一个参数与返回值都为空的block。

    3.四种情况

    两种队列和两种执行方式组合起来有四种效果,分别是同步并行 同步串行 异步并行 和异步串行,
    四种方式分别有不同的效果。在这写条件里,执行方式起主导作用,也就是同步和异步执行起决定因素。

    1)同步并行
    - (IBAction)syncconcurrence:(id)sender {
        
        NSLog(@"开始");
        
        //创建并行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //向队列中添加任务一
        dispatch_sync(queue, _block);
        
        //向队列中添加任务二
        dispatch_sync(queue, _block);
        
        //向队列中添加任务三
        dispatch_sync(queue, _block);
        
        NSLog(@"等不等我");
    }
    

    运行效果如下

    2017-09-13 00:25:32.730 GCD[1181:107620] 开始
    2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.732 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.732 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
    2017-09-13 00:25:32.733 GCD[1181:107620] 等不等我
    

    可以看到结果是按添加到队列里的顺序,依次在主线程执行。
    同步意味着阻塞,等队列里的任务执行完 才能继续往下执行NSLog(@"等不等我")这句代码,并且不会开辟新线程,尽量在原有线程里执行队列里的任务,虽然队列是并发队列,队列想要系统去开辟多个线程去执行,但是前面的执行方式已经限定,系统不能开辟新线程,只能在原有线程里面去执行队列里的任务,结果跟单线程一样,所以最终结果是依次执行队列里的任务。

    2)同步串行
    //同步串行
    - (IBAction)syncseria:(id)sender {
        
        NSLog(@"开始");
        
        //创建并行队列 这里使用NULL缺省表示创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
        
        //向队列中添加任务一
        dispatch_sync(queue, _block);
        
        //向队列中添加任务二
        dispatch_sync(queue, _block);
        
        //向队列中添加任务三
        dispatch_sync(queue, _block);
        
        NSLog(@"等不等我");
    
    }
    

    运行结果如下

    2017-09-13 00:29:42.657 GCD[1196:110692] 开始
    2017-09-13 00:29:42.657 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.657 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.658 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.659 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.659 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.660 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
    2017-09-13 00:29:42.660 GCD[1196:110692] 等不等我
    

    同步的意义同上,串行意味着按添加到队列里的顺序去执行队列里的任务,所以该组合效果与上面的效果是相同的。

    3)异步并行
    //异步并行
    - (IBAction)asyncconcurrence:(id)sender {
        
        
        NSLog(@"开始");
        
        //创建并行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //向队列中添加任务一
        dispatch_async(queue, _block);
        
        //向队列中添加任务二
        dispatch_async(queue, _block);
        
        //向队列中添加任务三
        dispatch_async(queue, _block);
        
        NSLog(@"等不等我");
    
        
    }
    
    2017-09-13 00:53:26.554 GCD[1251:127458] 开始
    2017-09-13 00:53:26.554 GCD[1251:127458] 等不等我
    2017-09-13 00:53:26.554 GCD[1251:128289] 0-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
    2017-09-13 00:53:26.554 GCD[1251:128296] 0-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
    2017-09-13 00:53:26.554 GCD[1251:128297] 0-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
    2017-09-13 00:53:26.557 GCD[1251:128289] 1-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
    2017-09-13 00:53:26.558 GCD[1251:128296] 1-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
    2017-09-13 00:53:26.559 GCD[1251:128297] 1-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
    
    

    异步并行执行不会阻塞当前代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队列里取出任务,然后开辟数个线程(线程数量不确定,由系统决定)去执行队列里的任务。

    4)异步串行
    //异步串行
    - (IBAction)asyncseria:(id)sender {
        
    
        NSLog(@"开始");
        
        //创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
        
        //向队列中添加任务一
        dispatch_async(queue, _block);
        
        //向队列中添加任务二
        dispatch_async(queue, _block);
        
        //向队列中添加任务三
        dispatch_async(queue, _block);
        
        NSLog(@"等不等我");
    
        
    }
    

    运行效果如下

    2017-09-13 00:53:44.740 GCD[1251:127458] 开始
    2017-09-13 00:53:44.740 GCD[1251:127458] 等不等我
    2017-09-13 00:53:44.741 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    2017-09-13 00:53:44.741 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    2017-09-13 00:53:44.742 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    2017-09-13 00:53:44.743 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
    

    与异步并行的相同点是都不会阻塞当前的代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队里里取出任务,开辟新线程去执行队列里的任务。区别是串行执行时候,队列里的任务只能按顺序依次执行,所以系统仅仅开辟一个新线程就可以完成任务。

    四种组合,在实际开发中应当如何选取?
    同步方式使用较少,因为同步方式会阻塞当前线程,并且按顺序执行队列任务,这样的效果跟没有使用队列的情况下并没有什么差别,同步执行也不会开辟新线程, 使用意义不是很大。
    异步使用场景较多,尤其是涉及到耗时操作,比如网络请求。在发出网络请求时候,我们肯定不能让主线程等待网络请求结果返回在继续执行,因此,这里要用异步dispatch_async请求。

    串行队列
        //创建并行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
        
        
        dispatch_async(queue, ^{
           
            //获取开始执行任务的相对时间  注意这个单位是纳秒
            uint64_t timeStrat = mach_absolute_time();
            NSLog(@"%llu",timeStrat);
            
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务1");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor redColor];
            });
            
        });
        
        dispatch_async(queue, ^{
            
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务2");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor greenColor];
            });
            
        });
    
        dispatch_async(queue, ^{
            
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务3");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor purpleColor];
            });
            
            //获取结束任务的相对时间 注意这个单位是纳秒
            uint64_t timeEnd = mach_absolute_time();
            NSLog(@"%llu",timeEnd);
        });
    

    执行结果如下:

    2017-09-13 15:58:25.534 GCD[3052:231602] 24762306847072
    2017-09-13 15:58:27.536 GCD[3052:231602] 已完成任务1
    2017-09-13 15:58:29.537 GCD[3052:231602] 已完成任务2
    2017-09-13 15:58:31.541 GCD[3052:231602] 已完成任务3
    2017-09-13 15:58:31.541 GCD[3052:231602] 24768313894166
    

    可以看到串行队列会按任务的添加顺序依次执行。每个任务会耗时2秒,完成队列所有任务要花费的时间约为6秒。

    并行队列

    仅需要把创建队列时候的第二个参数设置为DISPATCH_QUEUE_CONCURRENT即可

    //创建并行队列
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        
        dispatch_async(queue, ^{
           
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务1");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor redColor];
            });
            
        });
        
        dispatch_async(queue, ^{
            
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务2");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor greenColor];
            });
            
        });
    
        dispatch_async(queue, ^{
            
            //模拟网络请求 2秒后完成
            sleep(2);
            NSLog(@"已完成任务3");
            
            //回到主队列 也就是主线程里进行UI操作
            dispatch_async(dispatch_get_main_queue(), ^{
                
                self.view.backgroundColor = [UIColor purpleColor];
            });
            
        });
    
    

    执行效果如下

    2017-09-13 15:51:43.429 GCD[3022:227333] 已完成任务2
    2017-09-13 15:51:43.429 GCD[3022:227326] 已完成任务1
    2017-09-13 15:51:43.429 GCD[3022:227332] 已完成任务3
    

    可以看到并行不一定会按任务添加顺序执行,完全由系统取出任务,分配到不同线程去执行,所以先后顺序也会不同。

    相关文章

      网友评论

          本文标题:iOS多线程之GCD使用

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