美文网首页
iOS 多线程入门03--GCD

iOS 多线程入门03--GCD

作者: XieHenry | 来源:发表于2017-02-10 14:50 被阅读309次

    一.什么是GCD?

    1.GCD (Grand Central Dispatch) 是Apple公司开发的一种技术,它旨在优化多核环境中的并发操作并取代传统多线程的编程模式。
    2.GCD是基于C语言的线程管理方案,使用者无需过多参与线程的管理,只需要将想要执行的代码,添加到想要添加的调度队列即可。
    3.GCD主要用在后台执行较慢任务;延迟执行任务;以及在后台任务中,切换回主线程,更新UI。


    二.GCD的优势

    GCD是苹果公司为多核的并行运算提出的解决方案。
    GCD会自动利用更多的CPU内核(比如双核、四核)。
    GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。
    程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。


    三.任务和队列

    3.1 队列

    队列:用于存放要执行的任务。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在 GCD中有两种队列:

    • 串行队列(Serial Dispatch Queue)

    让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

    • 并发队列(Concurrent Dispatch Queue)

    可以让多个任务并发(同时)执行(自动开启多个线程执行任务)并发功能只有在异步(dispatch_async) 函数下才有效

    DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因为宏定义定义的为NULL。
    DISPATCH_QUEUE_CONCURRENT:表示并发
    

    3.2 任务

    任务:在线程中执行的操作,GCD中就是指Block中的代码。任务有两种方式:同步执行(sync)和异步执行(async)。区别在于是否会创建新的线程。同步和异步的主要区别在于会不会阻塞当前线程,直到Block中的任务执行完毕。

    • 同步执行:当前任务不完成,不会执行下个任务。
    • 异步执行:当前任务不完成,不会等待,同样可以执行下个任务。

    四.GCD的串行队列,并发队列和全局队列

    组合使用的一般是:
    1.串行队列,同步任务:不会开启新的线程,并且任务会顺序执行。
    2.串行队列,异步任务:会开启新的线程,任务会顺序完成。
    3.并发队列,同步任务:不会开启线程,并且会顺序执行。
    4.并发队列,异步任务:会开启线程,不会顺序执行。
    1和3的作用是一样的。

    4.1串行队列

    1.串行队列,同步任务(在新线程中执行任务,并且等待线程执行完毕再向后执行,几乎不用)
        /*
         会开线程吗?    会顺序执行吗?  
         不会               会           
         */
        //1.创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
        
        //2.添加任务到队列中执行
        for (int i=0; i<10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
    
    总结:不会开启新的线程,并且会顺序执行。
    2.串行队列,异步任务
    • 2.1
    
        /*
         会开线程吗?    会顺序执行吗?    come here位置确定吗?
           会             不一定            不确定
         */
    
        dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
        
        for (int i=0; i<10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);  //子线程
            });
        }
        NSLog(@"come here"); //在主线程,和队列没有任何关系
    
    • 2.2
        /*
         会开线程吗?    会顺序执行吗?    come here位置确定吗?
           会             不一定            相对不确定
         */
      dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_SERIAL);
        
        for (int i=0; i<10; i++) {
            NSLog(@"%d-----",i); //主线程,会打印完9之后,才会执行come here
            dispatch_async(queue, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i); //子线程
            });
        }
        NSLog(@"come here"); //在主线程,和队列没有任何关系
    
    2018-09-01 14:15:51.719251+0800 GCD演示[6004:3053552] 0-----
    2018-09-01 14:15:51.719581+0800 GCD演示[6004:3053552] 1-----
    2018-09-01 14:15:51.719694+0800 GCD演示[6004:3053552] 2-----
    2018-09-01 14:15:51.719791+0800 GCD演示[6004:3053552] 3-----
    2018-09-01 14:15:51.719882+0800 GCD演示[6004:3053552] 4-----
    2018-09-01 14:15:51.719970+0800 GCD演示[6004:3053552] 5-----
    2018-09-01 14:15:51.720057+0800 GCD演示[6004:3053552] 6-----
    2018-09-01 14:15:51.722579+0800 GCD演示[6004:3053552] 7-----
    2018-09-01 14:15:51.722707+0800 GCD演示[6004:3053552] 8-----
    2018-09-01 14:15:51.722798+0800 GCD演示[6004:3053552] 9-----
    2018-09-01 14:15:51.723057+0800 GCD演示[6004:3053552] come here
    2018-09-01 14:15:51.734849+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 0
    2018-09-01 14:15:51.735336+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 1
    2018-09-01 14:15:51.735606+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 2
    2018-09-01 14:15:51.741201+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 3
    2018-09-01 14:15:51.742350+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 4
    2018-09-01 14:15:51.742660+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 5
    2018-09-01 14:15:51.743100+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 6
    2018-09-01 14:15:51.743355+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 7
    2018-09-01 14:15:51.743598+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 8
    2018-09-01 14:15:51.743836+0800 GCD演示[6004:3053583] <NSThread: 0x10188dbf0>{number = 3, name = (null)} 9
    
    • 首先肯定是打印完9之后,才会打印come here,因为都是在主线程,然后打印数字和打印线程是交叉执行的。
    总结:会开启新的线程,任务会顺序完成。

    4.2并发队列

    1.并发队列,同步任务(不会开启新的线程,并发队列失去了并发的功能。)
        /*
       会开线程吗?        会顺序执行吗?    come here位置确定吗?
         不会                会                最后
         */
        
        //1.创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
        
        //2.添加任务到队列中执行
        for (int i=0; i<10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
        NSLog(@"come here");
    
    总结:不会开启线程,并且会顺序执行。
    2.并发队列,异步任务(执行较慢的任务,例如大量计算,网络请求等。)
        /*
         会开线程吗?    会顺序执行吗?    come here位置确定吗?
         会              不一定           come here 不确定
         */
        
        //1.创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("gcd", DISPATCH_QUEUE_CONCURRENT);
        
        //2.添加任务到队列中执行
        for (int i=0; i<10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@ %d",[NSThread currentThread],i);
            });
        }
        
        NSLog(@"come here");
    
    总结:会开启线程,不会顺序执行。

    4.3全局队列(本质上是并发队列)

          /*
         会开线程吗?    会顺序执行吗?    come here位置确定吗?
           会              不一定           最前面
         */
       //全局队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
        
        for (int i = 0; i< 10; i++) {
            dispatch_async(q, ^{
                NSLog(@"%@  %d",[NSThread currentThread],i);
            });
        }
        NSLog(@"come here");
    

    五.GCD的其他用法以及注意事项

    5.1异步主线程(用于在后台线程的任务将要完成时,切换到主线程更新UI)(不会开新线程)
     //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
     dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"在异步主线程中执行");
        });
    
    // 执⾏耗时的异步操作...
        dispatch_async( dispatch_get_global_queue(0, 0), ^{ //请求数据
           
            dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,执⾏UI刷新操作
                //对图片或别的操作进行赋值等,回到主线程
    
            });
        });
    
    5.2同步主线程(慎用)

    主要用途:只有在其它线程中才可能执行此方法,否则会死锁

        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@“在同步主线程中执行,慎用,否则会死锁”);
        });
    
    5.3延时执行线程
        //延迟
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"我在五秒后打印");
        });
    
    5.4单例模式(只执行一次)
    static GGT_Singleton *singleton = nil;  //在.m中保留一个全局的static的实例
    
    
    + (GGT_Singleton *)sharedSingleton {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            singleton = [[GGT_Singleton alloc]init];
        });
        
        return singleton;
    }
    
    5.5队列组

    首先:分别异步执行2个耗时的操作
    其次:等2个异步操作都执行完毕后,再回到主线程执行操作
    如果想要快速高效地实现上述需求,可以考虑用队列组

    dispatch_group_t group =  dispatch_group_create();
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 执行1个耗时的异步操作
            NSLog(@"%@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 执行1个耗时的异步操作
            NSLog(@"%@",[NSThread currentThread]);
    
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 等前面的异步操作都执行完毕后,回到主线程...
            NSLog(@"结束");
        });
    
    2018-09-01 18:17:15.119534+0800 003--GCD演示[6068:3101840] <NSThread: 0x11dd86970>{number = 5, name = (null)}
    2018-09-01 18:17:15.120041+0800 003--GCD演示[6068:3101840] <NSThread: 0x11dd86970>{number = 5, name = (null)}
    2018-09-01 18:17:15.120340+0800 003--GCD演示[6068:3101669] 结束
    
    5.6快速迭代(使用dispatch_apply函数能进行快速迭代遍历)
     dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){ //%zu用来输出size_t 类型
            // 执行10次代码,index顺序不确定
            NSLog(@"%zu",index);
        });
    

    六.GCD的总结

    主队列:

    dispatch_queue_t queue = dispatch_get_main_queue();
    

    全局并行队列:

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    

    手动创建串行队列:

    //DISPATCH_QUEUE_SERIAL: 表示串行,也可以使用NULL表示,因为宏定义定义的为NULL。
    dispatch_queue_t queue = dispatch_queue_create("a", NULL);
    

    手动创建并行队列:

    dispatch_queue_t queue = dispatch_queue_create("a", DISPATCH_QUEUE_CONCURRENT);
    

    异步:

    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    

    同步(慎用):

    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    

    需要注意的是:在MAC环境下,全局队列不需要释放,并发队列需要释放,方法是 dispatch_release(q)


    七.小结

    • 同步任务死锁:当前是在主线程,让主队列执行同步任务!
        //主队列是专门负责在主线程上调度任务的队列 --> 不会开线程
        //1.队列 --> 已启动主线程,就可以获取主队列
        dispatch_queue_t q = dispatch_get_main_queue();
        
        //2.同步任务  ==> 死锁
        dispatch_sync(q, ^{
            NSLog(@"能来吗? ");
        });
        NSLog(@"come here");
    

    解决办法:

      void (^task)() = ^{
            NSLog(@"这里%@",[NSThread currentThread]);
            //1.队列 --> 已启动主线程,就可以获取主队列
            dispatch_queue_t q = dispatch_get_main_queue();
            
            //2.同步任务
            dispatch_sync(q, ^{
                NSLog(@"能来吗? %@",[NSThread currentThread]);
            });
            
            NSLog(@"come here");
        };
        
        dispatch_async(dispatch_get_global_queue(0, 0), task);
    
    • 竞争&同步:两个线程抢夺同一个资源,就会竞争,为了防止竞争,一个线程拥有资源的时候,会对资源加锁,另一个线程就要等待解锁以后再拥有这个资源,这叫同步。

    • 死锁:两个线程互相等待对方释放资源。

    • 主线程&后台线程:主线程也叫前台线程,程序启动的默认线程,操作UI的线程。后台线程,即非主线程,用于不影响主线程的完成一些任务。

    • 同步&异步:同步执行线程,等待新线程执行完以后,再继续执行当前线程,很少用到。异步执行线程,在执行新线程的同时,继续执行当前线程,常用。

    iOS 多线程入门01--概念知识
    iOS 多线程入门02--NSThread
    iOS 多线程入门04--NSOperation

    相关文章

      网友评论

          本文标题:iOS 多线程入门03--GCD

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