iOS多线程GCD使用及总结

作者: 王技术 | 来源:发表于2017-06-26 15:21 被阅读166次

    这篇文章对iOS的多线程技术GCD的常用方法做了总结
    另一篇NSOperation在这里
    本文代码

    - GCD:

    简介:

    • 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
    • 纯C语言,提供了非常多强大的函数
    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    GCD两个核心概念:

    • 任务 : 干什么事情,执行什么操作
    • 队列 : 用什么方式(串行,并发)来存放任务

    首先介绍GCD执行任务时常用的两个函数:

    • 用同步的方式来执行任务:
     dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    • 用异步的方式来执行任务:
    ddispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    

    同步和异步的区别:
    同步 : 只在当前线程执行 , 不开其他线程
    异步 : 可以在新的线程中执行任务 , 有开线程的能力 (但是不一定开,要看用什么队列)

    然后是两种GCD队列:

    • 并发队列:
      可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      (并发功能只有用异步(dispatch_async)函数才有效)
    //创建并发队列:
    dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
    //获得全局并发队列:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    
    • 串行队列
      让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
    // 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
    dispatch_queue_t queue = dispatch_queue_create("name", NULL); 
    //使用dispatch_get_main_queue()获得主队列
    //主队列是GCD自带的一种特殊的串行队列
    //放在主队列中的任务,都会放到主线程中执行(不管是同步函数,还是异步函数)
    dispatch_queue_t queue = dispatch_get_main_queue();
    

    代码实践各种队列与函数的组合:

    • 同步函数 + 串行队列:
    //同步+串行
    -(void)syncSerial{
        //创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
        //用同步函数把任务放入串行队列:
        dispatch_sync(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    }
    

    打印结果:

    2017-06-26 13:25:53.744 HXThread[1628:247794] -------download1--<NSThread: 0x60800007b100>{number = 1, name = main}
    2017-06-26 13:25:53.745 HXThread[1628:247794] -------download2--<NSThread: 0x60800007b100>{number = 1, name = main}
    2017-06-26 13:25:53.745 HXThread[1628:247794] -------download3--<NSThread: 0x60800007b100>{number = 1, name = main}
    

    结论 :
    同步函数是在当前线程执行,不具备开线程的能力,然后串行队列又是的任务又是串行执行,所以没有开新的线程,大家一个一个的执行

    • 同步函数 + 并发队列:
        //获得全局并发队列
        dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //用同步函数 把任务加入并行队列
        //用同步函数把任务放入串行队列:
        dispatch_sync(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    

    打印结果:

    2017-06-26 13:34:57.588 HXThread[1674:254728] -------download1--<NSThread: 0x60800007fe80>{number = 1, name = main}
    2017-06-26 13:34:57.591 HXThread[1674:254728] -------download2--<NSThread: 0x60800007fe80>{number = 1, name = main}
    2017-06-26 13:34:57.594 HXThread[1674:254728] -------download3--<NSThread: 0x60800007fe80>{number = 1, name = main}
    

    结论:
    同步函数是在当前线程执行,不具备开线程的能力,就算你是并发队列也不好使,任务还是在当前线程,大家一个一个的来

    • 异步函数+串行队列:
    //异步函数 + 串行队列
    -(void)asyncSerial{
        //创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
        //用异步函数 将任务加入串行队列
        dispatch_async(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    }
    

    打印结果:

    2017-06-26 13:48:02.264 HXThread[1720:261890] -------download1--<NSThread: 0x600000066680>{number = 4, name = (null)}
    2017-06-26 13:48:02.264 HXThread[1720:261890] -------download2--<NSThread: 0x600000066680>{number = 4, name = (null)}
    2017-06-26 13:48:02.264 HXThread[1720:261890] -------download3--<NSThread: 0x600000066680>{number = 4, name = (null)}
    

    结论:
    异步函数具有开线程的能力,但是串行队列是挨着来,所以就开一个线程,大家在这个新线程中一个一个整

    • 异步函数+并发队列
        //创建一个并发队列
        dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
        //用异步函数 将任务加入并发队列
        dispatch_async(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    

    打印结果:

    2017-06-26 13:51:57.523 HXThread[1749:264765] -------download2--<NSThread: 0x60800007ea40>{number = 6, name = (null)}
    2017-06-26 13:51:57.523 HXThread[1749:264699] -------download1--<NSThread: 0x60800007e280>{number = 5, name = (null)}
    2017-06-26 13:51:57.523 HXThread[1749:264766] -------download3--<NSThread: 0x600000264e00>{number = 7, name = (null)}
    

    结论:
    异步函数已经有了开线程的能力,并发队列又是又是让任务并发执行,所以开了多条线程,大家一起干

    • 异步函数+主队列
        //获取主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        //用异步函数 把任务放到主队列中
        dispatch_async(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    

    打印结果:

    2017-06-26 13:56:52.117 HXThread[1788:268167] -------download1--<NSThread: 0x608000078d40>{number = 1, name = main}
    2017-06-26 13:56:52.121 HXThread[1788:268167] -------download2--<NSThread: 0x608000078d40>{number = 1, name = main}
    2017-06-26 13:56:52.121 HXThread[1788:268167] -------download3--<NSThread: 0x608000078d40>{number = 1, name = main}
    

    结论:
    虽然异步函数有开线程的能力,但是队列是主队列,谁也不好使,大家都在主线程执行,一个一个挨着来

    • 同步函数+主队列
        //获取主队列
        dispatch_queue_t queue = dispatch_get_main_queue();
        //用同步函数 将任务加入主队列
        dispatch_sync(queue, ^{
            NSLog(@"-------download1--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download2--%@",[NSThread currentThread]);
        });
        dispatch_sync(queue, ^{
            NSLog(@"-------download3--%@",[NSThread currentThread]);
        });
    

    没有打印结果.程序报错
    结论:
    当程序执行到NSLog(@"-------download1--%@",[NSThread currentThread]);这一行的时候
    这一行打印任务就加入到了主队列,因为又是在主线程,所以打印任务要在主线程的当前任务结束后立马执行
    当前任务就是当前方法
    但是当前方法要等打印结束之后才能结束
    所以打印任务等当前方法任务结束才能执行
    当前方法这个任务又等打印执行结束才能结束
    就卡死了..........

    以上就是两种函数和两种队列的组合使用,总结一下:
    • 同步函数不会开线程,不管是并发队列,还是串行队列
    • 异步函数有开线程的能力,串行队列开一条,并发队列开多条
    • 为什么说异步函数是有开线程的能力,而不是肯定开,就因为遇到主队列还是不开线程
    • 同步+主队列卡死...

    线程之间的通信:

    代码实现:

    //子下载图片,回到主线程更新UI
    NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *image = [UIImage imageWithData:data];
            //回到主线程更新UIUI
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = image;
            });
    });
    

    GCD其他常用函数:

    • 一次性代码:
    static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"这里的代码在程序执行过程中,只会执行一次");
        });
    
    • 延时;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"延时2秒后执行这里的代码");
        });
    
    • 快速遍历
    //多线程快速遍历数组
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"------索引:%zu------%@",index,[NSThread currentThread]);
        });
    
    • 队列组
      类似任务依赖
        //全局并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //创建一个队列组
        dispatch_group_t group = dispatch_group_create();
        //下载图片
        dispatch_group_async(group, queue, ^{
            // 图片的网络路径
            NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
            // 加载图片
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 生成图片
            self.image1 = [UIImage imageWithData:data];
        });
        dispatch_group_async(group, queue, ^{
            // 图片的网络路径
            NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
            // 加载图片
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 生成图片
            self.image2 = [UIImage imageWithData:data];
        });
        //当group中的任务都执行完了以后,就会执行group中notify中的内容
        dispatch_group_notify(group, queue, ^{
            //合成图片
            UIGraphicsBeginImageContext(CGSizeMake(100, 100));
            [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
            [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imageView.image = image;
            });
        });
    
    • GCD定时器:
        //定时器在哪个队列进行
        dispatch_queue_t queue = dispatch_get_main_queue();
        //创建定时器
        dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //定时器的开始时间
        dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
        //设置时间间隔
        uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
        dispatch_source_set_timer(timer, start, interval, 0);
    
        // 设置回调
        dispatch_source_set_event_handler(self.timer, ^{
            NSLog(@"------------%@", [NSThread currentThread]);
        });
        
        // 启动定时器
        dispatch_resume(self.timer);
    

    值得注意的是
    GCD定时器时间间隔是纳秒
    1秒 = 1*10的九次方
    不过GCD为我们提供了一个宏 NSEC_PER_SEC
    (1.0 * NSEC_PER_SEC) 就是一秒
    GCD 定时器不受 RunLoop 运行模式的影响
    有关 RunLoop 和定时器的关系, 请看我这篇文章:
    http://www.jianshu.com/p/5ef8f28025b9

    谢谢阅读
    有不合适的地方请指教
    喜欢请点个赞
    抱拳了!

    相关文章

      网友评论

        本文标题:iOS多线程GCD使用及总结

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