iOS多线程——GCD

作者: 未之 | 来源:发表于2017-02-27 14:29 被阅读371次

    在iOS中,实现多线程的方式有很多,比如GCD,NSOperation,NSThread等等,但是一直对线程的概念模糊,今天就根据代码例子来了解iOS中GCD的用法和原理。

    GCD是和block紧密相连的,所以最好先了解下block。
    GCD是C level的函数,这意味着它也提供了C的函数指针作为参数,方便了C程序员。

    下面首先来看GCD的使用:
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    async表明异步运行,block代表的是你要做的事情,queue则是你把任务交给谁来处理了。(除了async,还有sync,delay,本文以async为例)。

    dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。

    之所以程序中会用到多线程是因为程序往往会需要读取数据,然后更新UI。为了良好的用户体验,读取数据的操作会倾向于在后台运行,这样以避免阻塞主线程。GCD里就有三种queue来处理。

    GCD Queue分为三种:

    • The main queue:主队列,主线程就是在个队列中,系统默认就有一个串行队列
    • Global queues: 全局并发队列
    • 用户队列:是用函数dispatch_queue_create创建的自定义队列

    而用户队列又分为下面两种:
    (1)Serial Dispatch Queue
    线程池只提供一个线程用来执行任务,所以后一个任务必须等到前一个任务执行结束才能开始。(DISPATCH_QUEUE_SERIAL)

    (2)Concurrent Dispatch Queue
    线程池提供多个线程来执行任务,所以可以按序启动多个任务并发执行。(DISPATCH_QUEUE_CONCURRENT)

    关于同步和异步

    • dispatch_sync
      则调用用 dispatch_sync的线程会等dispatch_sync的对内容执行完再继续执行。dispatch_sync函数不会立即返回,即阻塞当前线程,等待block同步执行完成。

    • dispatch_async
      调用dispatch_async的线程不会的等dispatch_async的内容,自己继续执行。dispatch_async函数会立即返回, block会在后台异步执行。

    关于并发和并行

    • 并行:是多个任务在同一个时间片段内同时进行,比如说你一边吃饭一边打电话一遍看电视

    • 并发:是多个任务在同一个时间的点上可以切换进行,比如说你先吃着饭,然后电话来了,停下吃饭立马打电话,打完后立马切换到看电视,这个你在同一个线条内只有一个任务在执行

    图示关于并发和并行

    并行 并发

    下面就以代码实例来分析GCD用法

    实例一:DISPATCH_QUEUE_SERIAL串行队列

    /**
     DISPATCH_QUEUE_SERIAL是每次运行一个任务,可以添加多个,执行次序FIFO。
     **/
    - (void)test1
    {
        NSDate *date = [NSDate date];
        
        NSString *daStr = [date description];
        
        const char *queueName = [daStr UTF8String];
        
        //DISPATCH_QUEUE_SERIAL等同于NULL
        dispatch_queue_t myQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
        
        [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(addTime) userInfo:nil repeats:YES];
        
        //在main_queue中异步将任务放到myQueue队列里
        //放入myQueue的顺序是按代码的执行顺序:任务一,任务二,任务三(因为main_queue是顺序执行)
        //在myQueue取出的顺序是按照FIFO的顺序
        //因为这是一个串行的队列,所以取出后的任务是一个接着一个执行的
        
        
        //执行结果
        //[NSThread sleepForTimeInterval:6]~~~~~~~~6
        //[NSThread sleepForTimeInterval:3]~~~~~~~~9
        //[NSThread sleepForTimeInterval:12]~~~~~~~~21
    
        
        //任务一:
        dispatch_async(myQueue, ^{
            
            [NSThread sleepForTimeInterval:6];
            
            NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);
            
        });
        
        //任务二:
        dispatch_async(myQueue, ^{
            
            [NSThread sleepForTimeInterval:3];
            
            NSLog(@"[NSThread sleepForTimeInterval:3]~~~~~~~~%d", t);
            
        });
        
        //任务三:
        dispatch_async(myQueue, ^{
            
            [NSThread sleepForTimeInterval:12];
            
            NSLog(@"[NSThread sleepForTimeInterval:12]~~~~~~~~%d", t);
            
        });
    }
    
    -(void)addTime {
        
        ++t;
    }
    
    

    实例二:全局的并发队列dispatch_get_global_queue

    /**
     可以同时运行多个任务,每个任务的启动时间是按照加入queue的顺序,结束的顺序依赖各自的任务.
     **/
    - (void)test2
    {
        dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            
        //在main_queue中异步将任务放到myQueue
        //放入myQueue的顺序是:任务一,任务二,任务三
        //在myQueue中取出的顺序是FIFO的原则:任务一,任务二,任务三
        //取出的任务放到线程里执行,因为这是一个并行的队列,所以任务可以同时运行
        
        //执行的结果
        //执行的结果可以是 1 2 3 的随机组合
        
        //任务一:
        dispatch_async(myQueue, ^{
            
            NSLog(@"~~~~~~~~~1");
            
        });
        
        //任务二:
        dispatch_async(myQueue, ^{
            
            NSLog(@"~~~~~~~~~2");
            
        });
        
        //任务三:
        dispatch_async(myQueue, ^{
            
            NSLog(@"~~~~~~~~~3");
            
        });
    }
    

    实例三:DISPATCH_QUEUE_SERIAL串行队列

    /**
     串行队列的异步任务:使用一个子线程依次执行。
      对比一下dispatch_async和dispatch_sync输出的i的顺序和线程的地址
     **/
    - (void)test3
    {
        dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
        
        for (int i = 0; i < 3; i++) {
            
            //在main_queue中将i=0,1,2的任务异步加入queue队列中
            //加入queue的顺序是:i=0, i=1, i=2
            //所以在queue中取出的顺序是:i=0,i=1,i=2
            //因为queue是同步队列,所以三个任务放到同一个线程中依次执行
            
            //运行结果
            //~~~i=0~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
            //<NSThread: 0x600000068d00>{number = 1, name = (null)}
            //~~~i=1~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
            //<NSThread: 0x600000068d00>{number = 1, name = (null)}
            // ~~~i=2~~~~<NSThread: 0x600000076940>{number = 3, name = (null)}
            //<NSThread: 0x600000068d00>{number = 1, name = (null)}
    
            //当是使用DISPATCH_QUEUE_SERIAL和dispatch_sync同步的时候,三个任务执行的线程就是当前的mainThread中执行的
    
            dispatch_async(queue, ^{
                
                NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
                
                NSLog(@"%@",[NSThread mainThread]);
                
            });
        }
    }
    

    实例四:DISPATCH_QUEUE_CONCURRENT并发队列

    /**
     并行队列的异步任务:使用多个子线程无序执行,一般任务较少时几个任务就开几个线程,较多时则开部分线程。
     应用:一系列的异步任务没有先后顺序,结束无序
     对比一下dispatch_async和dispatch_sync输出的线程的地址
     **/
    - (void)test4
    {
        
        dispatch_queue_t queue = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
        
        //在main_queue中异步的将i=0,i=1,i=2的任务加入到queue中
        //加入queue的顺序是i=0,i=1,i=2
        //在queue中取出的顺序是i=0,i=1,i=2
        //因为queue是并发队列,所以有多条的线程来执行三个任务,thread的执行的顺序不定
        
        //当使用dispatch_sync时,执行的顺序又成了i=0,i=1,i=2
        
        for (int i = 0; i < 3; i++) {
            
            dispatch_async(queue, ^{
                
                NSLog(@"~~~i=%d~~~~%@", i, [NSThread currentThread]);
                
                NSLog(@"%@",[NSThread mainThread]);
                
            });
        }
    }
    

    实例五:dispatch_group_t组队列

    /**
     dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
     dispatch_group_async会监听最终的任务完成后,并通知一个线程
     这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成了。
    注意这里不是监听queue里所有的任务完成,而是添加到组里的任务,这个任务是在这个queue里,同时也在这个组里,组里所有的任务的完成并不代表queue里所有的任务的完成。
     下面是一段例子代码:
     注意:当queue是global(或者DISPATCH_QUEUE_CONCURRENT)队列和DISPATCH_QUEUE_SERIAL队列时线程的区别
     **/
    - (void)test5
    {
        
        //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"task1~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
            
        });
        
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"task2~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
            
        });
        
        dispatch_group_async(group, queue, ^{
            
            NSLog(@"task3~~~~currentThread=%@~~~~~~mainThread%@", [NSThread currentThread], [NSThread mainThread]);
            
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            
            NSLog(@"回到主线程");
            
        });
    }
    

    实例六:dispatch_apply重复

    /**
     dispatch_apply:执行某个代码片段N次
     
     重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。
     多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
     
     dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
     运行结果:
     array[0]~~~~~~~~~~~~~~0
     array[3]~~~~~~~~~~~~~~3
     array[2]~~~~~~~~~~~~~~2
     array[1]~~~~~~~~~~~~~~1
     array[4]~~~~~~~~~~~~~~4
     array[5]~~~~~~~~~~~~~~5
     
     dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
     运行结果:
     array[0]~~~~~~~~~~~~~~0
     array[1]~~~~~~~~~~~~~~1
     array[2]~~~~~~~~~~~~~~2
     array[3]~~~~~~~~~~~~~~3
     array[4]~~~~~~~~~~~~~~4
     array[5]~~~~~~~~~~~~~~5
     **/
    - (void)test6
    {
        
        NSArray *array = @[@"0",@"1",@"2",@"3",@"4",@"5"];
        
        dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
        
        //打印done是在dispatch_apply里所有的任务执行完毕之后才会执行
        dispatch_async(queue2, ^(){
            
            //放在dispatch_apply里面的任务的执行顺序完全依赖于queue1的队列串发还是并发
            dispatch_apply([array count], queue1, ^(size_t index) {
                
                NSLog(@"array[%ld]~~~~~~~~~~~~~~%@", index, array[index]);
                
            });
            
            NSLog(@"done");
        });
    }
    

    实例七:dispatch_barrier_async

    /**
     dispatch_barrier_async的使用
     
     dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
     
     只有当这个队列为自己创建的并发队列(DISPATCH_QUEUE_CONCURRENT)时才会有这种效果
     执行结果(一)为:
     dispatch_async2
     dispatch_async1
     dispatch_barrier_async
     dispatch_async3
     
     如果使用dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     根据官方文档指出,这个时候的dispatch_barrier_async完全等同于dispatch_async
     执行结果(二)为:
     dispatch_barrier_async
     dispatch_async3
     dispatch_async2
     dispatch_async1
     
     在使用DISPATCH_QUEUE_SERIAL串行队列时,完全就按照FIFO的顺序执行了
     执行结果(三)为:
     2015-12-23 16:53:04.574 NSURLDemo[67716:3661098] dispatch_async1
     2015-12-23 16:53:05.577 NSURLDemo[67716:3661098] dispatch_async2
     2015-12-23 16:53:05.578 NSURLDemo[67716:3661098] dispatch_barrier_async
     2015-12-23 16:53:07.085 NSURLDemo[67716:3661098] dispatch_async3
     
     不仅有dispatch_barrier_async方法,还有dispatch_barrier_sync方法,两个方法的区别是:
     dispatch_barrier_async当他把这个barrier添加到队列后,当前队列不用等待block的执行返回
     而dispatch_barrier_sync需要等待block的内容执行完毕之后再继续下面的执行
     **/
    - (void)test7
    {
        dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
        
        //dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_async(queue, ^{
            
            [NSThread sleepForTimeInterval:3];
            
            NSLog(@"dispatch_async1~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
            
        });
        
        dispatch_async(queue, ^{
            
            [NSThread sleepForTimeInterval:1];
            
            NSLog(@"dispatch_async2~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
    
            
        });
        
        dispatch_barrier_async(queue, ^{
            
            [NSThread sleepForTimeInterval:0.5];
            
            NSLog(@"dispatch_barrier_async~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
    
            
        });
        
        dispatch_async(queue, ^{
            
            [NSThread sleepForTimeInterval:1];
            
            NSLog(@"dispatch_async3~~~~~~~currentThread=%@~~~~~~~mainThread=%@",[NSThread currentThread], [NSThread mainThread]);
    
        });
    }
    

    实例八:dispatch_once一次使用函数

    /**
     dispatch_once
     dispatch_once这个函数,它可以保证整个应用程序生命周期中某段代码只被执行一次!
     **/
    - (void)test8
    {
        static dispatch_once_t onceToken;
        
        dispatch_once(&onceToken, ^{
            
            // code to be executed once
        });
    }
    

    实例九:dispatch_after延迟函数

    /**
     dispatch_after
     有时候我们需要等个几秒钟然后做个动画或者给个提示,这时候可以用dispatch_after这个函数:
     **/
    -(void)test9
    {
        double delayInSeconds = 2.0;
        
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
        
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            
            // code to be executed on the main queue after delay
        });
    }
    

    实例十:dispatch_set_target_queue转换队列

    /**
      dispatch_set_target_queue
      通过dispatch_set_target_queue函数可以设置一个dispatch queue的优先级,或者指定一个dispatch source相应的事件处理提交到哪个queue上。
      它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
      dispatch_set_target_queue(dispatchA, dispatchB);
      那么dispatchA上还未运行的block会放到dispatchB上,然后由dispatchB来进行管理运行。
     **/
    
      dispatch_set_target_queue(serialQ, globalQ);
    

    实例十一:dispatch_group_wait等待

    //dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。
    
    //dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    -(void)test10
    {
        
        dispatch_group_t group = dispatch_group_create();
        
        //注意queue为DISPATCH_QUEUE_CONCURRENT 和 DISPATCH_QUEUE_SERIAL时当前的线程
        
        dispatch_queue_t queue = dispatch_queue_create([@"com.queue" UTF8String], DISPATCH_QUEUE_SERIAL);
        
        //每个dispatch_group_enter对应一个dispatch_group_leave完成group内所有的任务则发送通知
        
        //进入group
        
        dispatch_group_enter(group);
        
        dispatch_async(queue, ^{
            
            NSLog(@"task1~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
            
            //离开group
            
            dispatch_group_leave(group);
            
        });
        
        
        dispatch_group_enter(group);
        
        dispatch_async(queue, ^{
            
            NSLog(@"task2~~~~~currentThread=%@~~~~~mainThread=%@", [NSThread currentThread], [NSThread mainThread]);
            
            dispatch_group_leave(group);
            
        });
        
        NSLog(@"~~~~~~~~~~~~~~~~~~before group wait");
        
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
        NSLog(@"~~~~~~~~~~~~~~~~~~after group wait");
        
        dispatch_group_notify(group, queue, ^{
            
            NSLog(@"~~~~~~~~~~~~~allGroupFinish");
            
        });
    }
    

    补充:dispatch_sync(dispatch_get_main_queue(),...)造成死锁的原因

    当这段代码放在主线程里,也即dispatch_get_main_queue()中,执行到sync时向dispatch_get_main_queue()插入同步thread,sync会等到里面的block执行完成才返回。sync又在主队列里面,是个串行队列,sync是后面才加入的,前面一个是主线程,所以sync想执行block必须等待前一个主线程执行完成,而主线程却在等待sync返回,才能执行后续工作,从而造成死锁。

    相关文章

      网友评论

      • SuAdrenine:请问有源码吗,我自己照着写,跑出来结果不对,比如第一个示例,NSLog(@"[NSThread sleepForTimeInterval:6]~~~~~~~~%d", t);对于打印6-3-12,数出的t都是0:pensive:
        未之:@Adrenine代码给我看一下吧,我测试的并没有什么问题
        SuAdrenine:@未之 有的,我新建了一个.h和.m,然后在.m定义了一个全局的static变量,new一个该文件,调用test方法
        未之:你的t有定义全局变量吧?或者你定义为一个static变量也可以

      本文标题:iOS多线程——GCD

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