多线程之GCD

作者: 6ffd6634d577 | 来源:发表于2016-04-27 18:21 被阅读36次
    一、GCD 核心概念

    Grand Central Dispatch。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案,老少咸宜,实在是居家旅行、杀人灭口,必备良药。

    • GCD的优势

    • GCD是苹果公司为多核的并行运算提出的解决方案

    • GCD会自动利用更多的CPU内核(比如双核、四核)

    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

    • GCD中有2个核心概念

    • 任务:执行什么操作,任务使用 block 封装

    • 队列:用来存放(调度)任务

    • GCD的使用就2个步骤
    • 定制任务

    • 确定想做的事情

    • 将任务添加到队列中

    • GCD会自动将队列中的任务取出,放到对应的线程中执行

    • 任务的取出遵循队列的FIFO原则:先进先出,后进后出

    • 执行任务的函数
    • 异步 dispatch_async

    1.不用等待当前语句执行完毕,就可以执行下一条语句
    2.会开启线程执行 block 的任务
    3.异步是多线程的代名词

    • 同步 dispatch_sync

    1.必须等待当前语句执行完毕,才会执行下一条语句
    2.不会开启线程
    3.在当前执行 block 的任务

    • 同步和异步的区别

    • 同步:不开线程,必须等待当前语句执行完毕,才会执行下一条语句

    • 异步:开线程,不用等待当前语句执行完毕,就可以执行下一条语句

    • 队列 - 负责调度任务

    • 串行队列

    1.一次只能"调度"一个任务
    2.dispatch_queue_create("abc", NULL);

    • 并发队列

    1.一次可以"调度"多个任务
    2.dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);

    • 主队列

    1.专门用来在主线程上调度任务的队列
    2.不会开启线程
    3.在主线程空闲时才会调度队列中的任务在主线程执行
    4.dispatch_get_main_queue()

    • 队列的选择
    • 多线程的目的:将耗时的操作放在后台执行!
    • 串行队列,只开一条线程,所有任务顺序执行

    如果任务有先后执行顺序的要求
    效率低 -> 执行慢-> "省电"
    有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"

    • 并发队列,会开启多条线程,所有任务不按照顺序执行

    如果任务没有先后执行顺序的要求
    效率高 -> 执行快 -> "费电"
    WIFI,包月

    小结:

    • 开不开线程由执行任务的函数决定
      异步开,异步是多线程的代名词
      同步不开
    • (异步)开几条线程由队列决定
      ○ 串行队列开一条线程
      ○ 并发队列开多条线程,具体能开的线程数量由底层线程池决定
      iOS 8.0 之后,GCD 能够开启非常多的线程
      iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
    二.队列详解

    1、串行队列

    特点
    以先进先出的方式,顺序调度队列中的任务执行。
    无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务。
    
    创建
    dispatch_queue_tqueue=dispatch_queue_create("abc",DISPATCH_QUEUE_SERIAL);
    dispatch_queue_tqueue = dispatch_queue_create("abc", NULL);
    
    1、串行队列
    同步执行
    /*
     提问:是否开线程?是否顺序执行?come here 的位置?
     结果:同步不开线程
    */
    - (void)gcdDemo1{
     //创建串行队列
     dispatch_queue_tqueue = dispatch_queue_create("itcast.cn", DISPATCH_QUEUE_SERIAL);
     //同步执行任务
    
       for (int index = 0; index < 10; index ++) {
            dispatch_sync(queue, ^{
            NSLog(@"拼命下载中.....%@,i = %d",[NSThread currentThread],index);
      });
     }
            NSLog(@"come here");
    }
    
    
    2、串行队列  异步执行
    /**
     提问:是否开线程?是否顺序执行?come here 的位置?
     结果: 会开线程,顺序执行
     */
    -(void)gcdDemo2{
        //创建串行队列
       dispatch_queue_tqueue = dispatch_queue_create("itcast.cn", NULL);
        //异步执行任务
       for (int index = 0; index < 10; index ++) {
             dispatch_async(queue, ^{
              NSLog(@"拼命下载中.....%@,i = %d",[NSThread currentThread],index);
      });
        }
               NSLog(@"come here");
    }
    

    2、并发队列

    特点
    以先进先出的方式,并发调度队列中的任务执行。
    如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务。
    如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行。
    
    创建
    dispatch_queue_t queue = dispatch_queue_create("cn.itcast", DISPATCH_QUEUE_CONCURRENT);
    
    /**
      开不开线程由执行任务的函数决定
      — 同步不开
      - 异步开
     
      (异步)开几条线程由队列决定
     - 串行队列,只开一条
     - 并发队列,开多条,由底层线程池决定
     
     在iOS7.0之前,GCD底层线程池一般最多会开5到6条。
     在iOS8.0之后,GCD底层线程池要多少给多少。
     */
    
    /**
     *  并发行队列异步执行
     *  问:会不会开线程? 开几条线程? 顺序执行?
        结果:会开,开多条(由底层线程池决定) 并发
     */
    - (void)gcdDemo2 {
        // 创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i < 10; ++i) {
            // 将任何添加到队列中
            dispatch_async(queue, ^ {
                NSLog(@"%@--%d",[NSThread currentThread],i);
            });
        }
        NSLog(@"over");
    }
    
    
    /**
     *  并发队列同步执行
        问:会开线程吗?over是在后面还是前面输出?
        猜:不会 后面输出  
        全中
     */
    - (void)gcdDemo1 {
        // 创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
        
        for (int i = 0; i < 10; ++i) {
            // 将任务添加到队列中--同步执行
            dispatch_sync(queue, ^ {
                NSLog(@"%@--%d",[NSThread currentThread],i);
            });
        }
        NSLog(@"over");
    }
    
    

    3、主队列

    特点
    Ø 专门用来在主线程上调度任务的队列。
    Ø 不会开启线程。
    Ø 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行。
    Ø 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度。
    
    主队列的获取
    Ø 主队列是负责在主线程调度任务的 
    Ø 会随着程序启动一起创建
    Ø 主队列只需要获取不用创建
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent(UIEvent *)event {
         [self gcdDemo1];
         NSLog(@"睡");
         [NSThreadsleepForTimeInterval:1.0];
         NSLog(@"醒");
    
    }
    
    1、主队列,异步执行
    - (void)gcdDemo1{
        dispatch_queue_tqueue = dispatch_get_main_queue();
       for (int i =0; i < 10;++i) {  
       dispatch_async(queue, ^{
          NSLog(@"%@ - %d", [NSThread currentThread], i); 
      });    
      NSLog(@"--->%d", i);
        }
        NSLog(@"come here");
    }
    提示:在主线程空闲时才会调度队列中的任务在主线程执行
    
     
    
    2、主队列,同步执行
    - (void)gcdDemo2{
     //获得主队列
        dispatch_queue_tqueue = dispatch_get_main_queue();
       NSLog(@"!!!");
        //同步执行任务
       dispatch_sync(queue, ^{
            NSLog(@"%@", [NSThreadcurrentThread]);
        });
        NSLog(@"come here");
    }
    **主队列和主线程相互等待会造成死锁**
    
     
    
    3、主队列调度同步执行不死锁,把主队列的任务丢到子线程
    - (void)gcdDemo3 {
        dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue",DISPATCH_QUEUE_CONCURRENT);
       void (^task)() = ^ {
            NSLog(@"%@===>",[NSThreadcurrentThread]);
           dispatch_sync(dispatch_get_main_queue(), ^{
             NSLog(@"死?");     
      });
       };
       dispatch_async(queue, task);
    }
    主队列在主线程空闲时才会调度队列中的任务在主线程执行
    

    4、同步任务的作用

    同步任务,可以让其他异步执行的任务,依赖某一个同步任务
    
    例如:在用户登录之后,再异步下载文件!
    
    - (void)gcdDemo1 {
        dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
    
        //所有的耗时操作都要异步执行
        //同步任务不执行完,队列不会调度后续的任务
       dispatch_sync(queue, ^{
            NSLog(@"登录 %@", [NSThread currentThread]);
        });
    
       dispatch_async(queue, ^{
            NSLog(@"下载 A %@",
    [NSThreadcurrentThread]);
        });
    
       dispatch_async(queue, ^{
    
            NSLog(@"下载 B %@",
    [NSThreadcurrentThread]);
        });
    }
    
     
    
    代码调整,让登录也异步执行
    
    - (void)gcdDemo2 {
        dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
       void (^task)() = ^{  
      dispatch_sync(queue, ^{
              NSLog(@"登录 %@", [NSThread currentThread]);  
      });     
      dispatch_async(queue, ^{ 
              NSLog(@"下载 A %@", [NSThread currentThread]);  
      });     
      dispatch_async(queue, ^{
              NSLog(@"下载 B %@", [NSThread currentThread]);   
      });
        };
       dispatch_async(queue, task);
    }
    

    5、全局队列

    全局队列是系统为了方便程序员开发提供的,其工作表现与并发队列一致。
    全局队列 异步任务
    /**
     提问:是否开线程?是否顺序执行?come here的位置?
     */
    - (void)gcdDemo8 {
        // 1. 队列
        dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    
        // 2. 执行任务
        for (inti = 0; i < 10; ++i) {
            dispatch_async(q, ^{
                NSLog(@"%@ - %d", [NSThread currentThread], i);
            });
        }
        NSLog(@"come here");
    }
    运行效果与并发队列相同
    
     
    
    全局队列和并发队列的比较
    
    全局队列
    没有名称
    无论 MRC & ARC
    都不需要考虑释放
    日常开发中,建议使用"全局队列"
    
     
    并发队列
    有名字,和 NSThread 的 name 属性作用类似
    如果在 MRC开发时,需要使用 dispatch_release(q); 释放相应的对象
    dispatch_barrier 必须使用自定义的并发队列
    开发第三方框架时,建议使用并发队列
    
     
    
     dispatch_get_global_queue函数参数
    
    1.服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级
    
    iOS 8.0(新增)
    QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
    QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
    QOS_CLASS_DEFAULT 0x15,  默认(用来底层重置队列使用的,不是给程序员用的)
    QOS_CLASS_UTILITY 0x11,  实用工具(专门用来处理耗时操作!)
    QOS_CLASS_BACKGROUND 0x09, 后台
    QOS_CLASS_UNSPECIFIED 0x00,  未指定,可以和iOS 7.0 适配
    
    iOS 7.0
    DISPATCH_QUEUE_PRIORITY_HIGH 2  高优先级
    DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认优先级
    DISPATCH_QUEUE_PRIORITY_LOW (-2)  低优先级
    DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级
    
    2.为未来保留使用的,应该永远传入0
    
    结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0,
    0);
    

    6、Barrier异步

    注意:dispatch_barrier_async 必须使用自定义队列,否则执行效果和全局队列一致
    Ø 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
    Ø 适合于大规模的 I/O 操作
    
    准备工作
    @interface ViewController (){
        //加载照片队列
      dispatch_queue_t  _photoQueue;
    }
    @property (nonatomic, strong) NSMutableArray *photoList;
    @end
    
     
    
    @implementation ViewController
    - (NSMutableArray *)photoList {
       if (_photoList == nil) {    
      _photoList = [[NSMutableArray alloc] init];
        }
        return_photoList;
    }
    提示:NSMutableArray 是非线程安全的。 
    
    viewDidLoad 
    - (void)viewDidLoad {
        [superviewDidLoad];
        _photoQueue= dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);
    
       for (int i =0; i < 330;++i) {    
      [self loadPhotos:i];
        }
        NSLog(@"come here");
    } 
    
    模拟下载照片并在完成后添加到数组
    
    - (void)loadPhotos:(int)index
    {
        dispatch_async(_photoQueue, ^{   
      NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];
         NSString *path = [[NSBundlemainBundle] pathForResource:fileNameofType:nil];   
         UIImage *image = [UIImage imageWithContentsOfFile:path];
    
          NSLog(@"下载照片%@,%d", fileName,index);
           [self.photoList addObject:image];
        });
    
    }
    
    由于 NSMutableArray 是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况。 
    
    使用dispatch_barrier_async修复崩溃情况
    - (void)loadPhotos:(int)index
    {
        dispatch_async(_photoQueue, ^{  
        NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];     
        NSString *path = [[NSBundle mainBundle] pathForResource:fileNameofType:nil];     
        UIImage *image = [UIImage imageWithContentsOfFile:path];
          NSLog(@"下载照片%@,%d", fileName,index);
    
            dispatch_barrier_async(_photoQueue, ^{
              NSLog(@"添加图片 %@,%@", fileName,[NSThread currentThread]);     
          [self.photoList addObject:image];   
      });
        });
    }
    
    使用 dispatch_barrier_async 添加的 block
    会在之前添加的 block 全部运行结束之后,统一在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!
    
    

    Barrier工作示意图


    Snip20160427_7.png

    7.一次性执行

    有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”。
    
    // MARK: 一次性执行
    - (void)once {
       static dispatch_once_t onceToken;
       NSLog(@"%ld",onceToken);    
       dispatch_once(&onceToken, ^{
            [NSThread sleepForTimeInterval:1.0];
          NSLog(@"一次性吗?");
        });
        NSLog(@"come here");
    }
    dispatch
    内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的。
     
    以下代码用于测试多线程的一次性执行
    - (void)demoOnce {
       for (int i =0; i < 10;++i) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{  
          [self once];    
      });
        }
    }
    

    8.延迟操作

    #pragma mark - 延迟执行
    - (void)delay {
       /**
         从现在开始,经过多少纳秒,由"队列"调度  异步执行block 中的代码
         参数
         1. when    从现在开始,经过多少纳秒
         2. queue   队列
         3. block   异步执行的任务
      */
    
        dispatch_time_twhen = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0* NSEC_PER_SEC));
       void (^task)() = ^ {
            NSLog(@"%@", [NSThreadcurrentThread]);
        };
        //主队列
       dispatch_after(when, dispatch_get_main_queue(), task);
        //全局队列
    //  dispatch_after(when, dispatch_get_global_queue(0, 0), task);
        //串行队列
    //  dispatch_after(when, dispatch_queue_create("itheima", NULL),
    task);
        NSLog(@"come here");
    
    }
    

    三.调度组

    dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
    比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了
    
    - (void)group1 {
        //1. 调度组
        dispatch_group_tgroup = dispatch_group_create();
        
        //2. 队列
        dispatch_queue_tq = dispatch_get_global_queue(0, 0);
    
        //3. 将任务添加到队列和调度组
        dispatch_group_async(group, q, ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"任务 1 %@",[NSThreadcurrentThread]);
        });
        dispatch_group_async(group, q, ^{
            NSLog(@"任务 2 %@",[NSThreadcurrentThread]);
        });
        dispatch_group_async(group, q, ^{
            NSLog(@"任务 3 %@",[NSThreadcurrentThread]);
        });   
    
        //4. 监听所有任务完成
        dispatch_group_notify(group, q, ^{
            NSLog(@"OVER %@", [NSThreadcurrentThread]);
        });
        
       //5. 判断异步
        NSLog(@"come here");
    }
    
    #pragma mark - dispatch_group_async 的实现原理
    终端输入命令:man dispatch_group_async
    
    The dispatch_group_async() convenience function behaves like so:
    void
    dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
    {
        dispatch_retain(group);
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            block();
            dispatch_group_leave(group);
            dispatch_release(group);
        });
    }
    
    注意:进群组和离开群组一定要成对出现
    
    

    相关文章

      网友评论

        本文标题:多线程之GCD

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