美文网首页
iOS-GCD深度解析

iOS-GCD深度解析

作者: LZZ_IOS | 来源:发表于2020-01-03 14:36 被阅读0次

    一、GCD的优势

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

    二、GCD的重要概念

    GCD的两个核心概念:任务与队列

    1、任务:

    执行什么操作,执行任务有两种方式:同步执行函数和异步执行函数,他们之间的区别在于

    1.1、同步函数:

    只能在当前线程中执行任务,不具备开辟线程的能力,任务立刻执行,会阻塞当前线程并等待Block中的任务执行完毕,然后当前线程才能继续往下执行;

    1.2、异步函数:

    具备开辟线程的能力,但不一定会开辟新线程,当前线程会直接往下执行,不会等待Block中的任务执行完毕,所以不会阻塞当前线程;

    2、队列:

    用来存放任务,分为串行队列与并行队列

    2.1、串行队列

    让任务一个接一个执行(一个任务执行完毕之后,才能执行下一个任务);

    2.2、并发队列

    可以同时执行多个任务(自动开启多个线程同时执行任务),并发队列只有在异步函数(dispatch_async)才有效(同步函数不具备开辟线程的能力);

    三、GCD使用

    1、GCD使用步骤

    定制任务(确定想做的事情),将任务添加到队列中(GCD会自动将队列中的任务取出,放在对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出);

    2、创建队列

    创建队列

    //第一个参数<#const char * _Nullable label#>: C语言字符串,唯一标识
    //第二个参数<#dispatch_queue_attr_t  _Nullable attr#>: 队列的类型
    // 并行队列: DISPATCH_QUEUE_CONCURRENT
    // 串行队列: DISPATCH_QUEUE_SERIAL 或者 NULL
    dispatch_queue_t queue = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t  _Nullable attr#>);
    

    创建并行队列

    dispatch_queue_t queue = dispatch_queue_create("com.lz",DISPATCH_QUEUE_CONCURRENT);
    

    创建串行队列

    dispatch_queue_t queue = dispatch_queue_create("com.lz", DISPATCH_QUEUE_SERIAL);
    

    GCD默认已经提供了全局并行队列,供整个应用使用,可以无需手动创建

    //第一个参数:优先级
    //#define DISPATCH_QUEUE_PRIORITY_HIGH 2  //高
    //#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  //默认
    //#define DISPATCH_QUEUE_PRIORITY_LOW (-2) //低
    //#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN //后台
    //第二个参数:预留参数 0
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    获取主队列

    dispatch_queue_t queue = dispatch_get_main_queue();
    

    3、任务执行

    队列在queue中,任务在block块中
    开启同步函数 同步函数:会阻塞当前线程,没有开辟线程的能力

    dispatch_sync(queue,^{
      
    });
    

    开启异步函数 异步函数:不会阻塞当前线程,会开辟线程执行任务

    dispatch_async(queue,^{
      
    });
    

    4、任务和队列组合

    任务:同步函数 异步函数
    队列:串行 并行

    异步函数+并发队列:会开辟新的线程,并发执行

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_async(queue,^{
      NSLog(@"---%@---",[NSThread currentThread]);
    });
    

    异步函数+串行队列:只会开辟一条线程,任务串行执行

    dispatch_queue_t queue =  dispatch_queue_create("com.lz", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
           NSLog(@"---%@---",[NSThread currentThread]);
     });
    

    同步函数+并行队列:不会开辟线程,任务串行执行

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_sync(queue,^{
      NSLog(@"---%@---",[NSThread currentThread]);
    });
    

    同步函数+串行队列:不会开辟线程,任务串行执行

    dispatch_queue_t queue =  dispatch_queue_create("com.lz", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
       NSLog(@"---%@---",[NSThread currentThread]);
     });
    

    主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放在主线程中执行
    异步函数+主队列:不会开辟线程,任务串行执行

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue,^{
      NSLog(@"---%@---",[NSThread currentThread]);
    });
    

    同步函数+主队列:死锁

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue,^{
      NSLog(@"---%@---",[NSThread currentThread]);
    });
    

    因为主队列是串行队列,任务只能一个一个执行。而在主队列中添加同步任务,则会造成主队列等待同步任务执行完成,才能继续执行主队列任务,因此就会相互等待而发生死锁。将这个方法放入子线程中,则不会发生死锁,任务串行执行;

    5、任务队列组合总结

    同步函数和异步函数的执行顺序
    同步函数:立刻马上执行,会阻塞当前线程

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    NSLog(@"---start---");
    dispatch_sync(queue, ^{
        NSLog(@"---11---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"---22---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"---33---%@",[NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"---44---%@",[NSThread currentThread]);
    });
    NSLog(@"---end---");
    

    我们看一下输出

    ---start---
    ---11---<NSThread: 0x6000022f1340>{number = 1, name = main}
    ---22---<NSThread: 0x6000022f1340>{number = 1, name = main}
    ---33---<NSThread: 0x6000022f1340>{number = 1, name = main}
    ---44---<NSThread: 0x6000022f1340>{number = 1, name = main}
    ---end---
    

    同步函数会阻塞线程,立即执行
    异步函数:当前线程会直接往下执行,不会阻塞当前线程

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    NSLog(@"---start---");
    dispatch_async(queue, ^{
        NSLog(@"---11---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---22---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---33---%@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---44---%@",[NSThread currentThread]);
    });
    NSLog(@"---end---");
    

    我们来看一下输入

    ---start---
    ---end---
    ---11---<NSThread: 0x6000020f2180>{number = 5, name = (null)}
    ---22---<NSThread: 0x6000020e1500>{number = 6, name = (null)}
    ---33---<NSThread: 0x6000020de780>{number = 3, name = (null)}
    ---44---<NSThread: 0x6000020d6480>{number = 4, name = (null)}
    

    异步函数不会阻塞当前线程
    注意:GCD中开多少条线程是由系统根据CUP繁忙程度决定的,如果任务很多,GCD会开启适当的子线程,并不会让所有任务同时执行

    6、GCD线程间的通信

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (nonatomic, strong) UIImageView *imgV;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self _addSubviews];
    }
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_async(queue, ^{
            NSString *imgPath = @"图片地址";
            NSURL *url = [NSURL URLWithString:imgPath];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *img = [UIImage imageWithData:data];
            dispatch_async(dispatch_get_main_queue(), ^{
                self.imgV.image = img;
            });
        });
    }
    
    #pragma mark - setter UI
    -(void)_addSubviews{
        [self.view addSubview:self.imgV];
    }
    
    #pragma mark - getter settr
    -(UIImageView *)imgV{
        if (_imgV == nil) {
            _imgV = [[UIImageView alloc] init];
            _imgV.frame = self.view.frame;
        }
        return _imgV;
    }
    @end
    

    GCD线程间的通信非常简单,使用同步或异步函数,传入主队列即可。

    7、GCD中其他常用函数

    7.1、栅栏函数(控制任务的执行顺序)
    dispatch_barrier_async(queue,^{
    
    });
    

    我们来看一下栅栏函数的作用

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
      [self _barrier];
    }
    -(void _barrier{
      dispatch_queue_t queue = dispatch_queue_create("com.lz", DISPATCH_QUEUE_CONCURRENT);
      dispatch_async(queue, ^{
          for (NSInteger i = 0; i<3; i++) {
              NSLog(@"%zd-11--%@",i,[NSThread currentThread]);
          }
      });
      dispatch_async(queue, ^{
          for (NSInteger i = 0; i<3; i++) {
              NSLog(@"%zd-22--%@",i,[NSThread currentThread]);
          }
      });
      dispatch_barrier_async(queue, ^{
          NSLog(@"我是一个栅栏函数");
      });
      dispatch_async(queue, ^{
          for (NSInteger i = 0; i<3; i++) {
              NSLog(@"%zd-33--%@",i,[NSThread currentThread]);
          }
      });
      dispatch_async(queue, ^{
          for (NSInteger i = 0; i<3; i++) {
              NSLog(@"%zd-44--%@",i,[NSThread currentThread]);
          }
      });
    }
    

    我们来看一下输出

    0-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    0-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    1-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    1-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    2-11--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    2-22--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    我是一个栅栏函数
    0-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    0-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    1-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    1-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    2-44--<NSThread: 0x600001bbe2c0>{number = 6, name = (null)}
    2-33--<NSThread: 0x600001b95600>{number = 4, name = (null)}
    

    栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的

    7.2、延迟执行
    //第一个参数 延时时间
    //第二个参数 队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
            
    });
    
    7.3、一次性代码
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
            
    });
    
    7.4、倒计时
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    dispatch_source_set_timer(timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(timer, ^{
       
    });
    dispatch_resume(timer);
    
    7.5、队列组
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并行队列 
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 执行队列组任务
    dispatch_group_async(group, queue, ^{   
    });
    //队列组中的任务执行完毕之后,执行该函数
    dispatch_group_notify(group, queue, ^{
    });
    

    本文借鉴自
    Sky109iOS多线程--深度解析

    相关文章

      网友评论

          本文标题:iOS-GCD深度解析

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