zoukankan      html  css  js  c++  java
  • iOS_多线程(二)

      上篇中我们分享了NSThread、NSOperation&NSOperationQueue如何实现多线程,今天我们来看下第三种实现多线程的方式:GCD(Grand Central Dispatch)。
      GCD是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread, NSOperation的高效和强大的技术。程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
      
    一、GCD的简单介绍
      1.队列的类型:
           1.1主队列:main queue,主线程队列,更新UI的操作。是一个串行的队列。串行队列每次只处理一个任务,所以后一个任务必须等到前一个任务执行结束才能开始执行。
           1.2系统创建的并发队列:global queue(全局的,并行的队列),按照优先级分类。线程池提供多个线程来执行任务,所以按照FIFO的顺序并发启动、执行多个并发任务。
           1.3自定义的队列:可以根据需要创建串行队列或并发的队列。
      2.任务:
           2.1封装形式:block或C语言的的函数
           2.2添加到队列的方式:同步或异步(只对并发队列有区别)。不管是同步还是异步,如果将任务加到串行队列中都是一个接一个的执行,只有在并发队列中才有区别。
      3.特殊使用
           3.1仅执行一次     dispatch_once
           3.2延时执行     dispatch_after
           3.3成组的执行任务     dispatch_group
     
    二、GCD的两个核心概念。
      队列:队列负责管理开发者提交的任务,GCD队列始终以FIFO(先进先出)的方式来处理任务——但由于任务的执行时间并不相同,因为先处理的任务并不一定先结束。队列即可是串行队列,也可是并发队列,串行队列每次只能处理一个任务,而并发队列可同时处理多个任务,因为将会有多个任务并发执行。
      任务:任务就是用户提交给队列的工作单元,这些任务将会提交给队列底层维护的线程池执行,因此这些任务会以多线程的方式执行。
     
    三、GCD遵守的步骤
      1.创建队列
      2.将任务提交给队列
      将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
      提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出
     
    四、工作原理
      –让程序平行排队的特定任务,根据可用的处理资源,安排它们在任何可用的处理器上执行任务
      –要执行的任务可以是一个函数或者一个block
      –底层是通过线程实现的,不过程序员可以不必关注实现的细节
      –GCD中的FIFO队列称为dispatch queue,可以保证先进来的任务先得到执行
      –dispatch_group_notify可以实现监听一组任务是否完成,完成后得到通知
     
    五、工作流程图
    六、创建或访问队列

    1.串行队列

      GCD中获得串行有2种途径

      (1)使用dispatch_queue_create函数创建串行队列

      dispatch_queue_t  dispatch_queue_create(const char *label,  dispatch_queue_attr_t attr); 

      示例:

        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); 

        /**
          dispatch_queue_create函数参数解析
          队列的名称:队列的标识符
          队列的方式:
          DISPATCH_QUEUE_SERIAL   串行
          DISPATCH_QUEUE_CONCURRENT   并行
        */
        dispatch_release(queue); // 非ARC需要释放手动创建的队列
      (2)使用主队列(跟主线程相关联的队列)
      主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行使用dispatch_get_main_queue()获得主队列
      示例:
        dispatch_queue_t queue = dispatch_get_main_queue();
      如果将任务提交给主线程关联的串行队列,那么就相当于直接在程序主线程中去执行该任务。
    2.并发队列
      GCD中获得并行也有2种途径
       (1)使用dispatch_queue_create函数创建并发队列
      示例:
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
      如果将多个任务提交给并发队列,并发队列可以按FIFO的顺序启动多个并发执行的任务,由于任务的耗时长短并不相同,因此后提交的任务完全可能先完成。
      (2)GCD默认已经提供了全局的并发队列,供整个应用使用

      使用dispatch_get_global_queue函数获得全局的并发队列

      dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags); 

      示例:

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列
        这个参数是留给以后用的,暂时用不上,传个0。
        第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列。
     

    七、案例分析

      案例1:使用GCD模拟售票线程。

    #import "ViewController.h"
    
    @interface ViewController ()
    {
        NSInteger _tickets;
    }
    @property (weak, nonatomic) IBOutlet UITextView *textView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _tickets = 20;
        self.textView.text = @"";
        self.textView.layoutManager.allowsNonContiguousLayout = NO;
        
        //用GCD创建售票线程
        /**
         *  队列的名称:队列的标识符
            队列的方式:
            DISPATCH_QUEUE_SERIAL   串行
            DISPATCH_QUEUE_CONCURRENT   并行
         */
        //1.创建自定义队列
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
        //2.往队列中添加任务
        dispatch_async(queue, ^{
            [self gcdSaleMethod:@"售票GCD-1"];
        });
        dispatch_async(queue, ^{
            [self gcdSaleMethod:@"售票GCD-2"];
        });
        //如果采用同步方式,会将所有线程添加到主线程。
    }
    -(void)appendTextView:(NSString *)text
    {
        //获取现有的内容
        NSMutableString *string = [NSMutableString stringWithString:self.textView.text];
        NSRange range = NSMakeRange(string.length, 1);
        //设置TextView
        [string appendString:[NSString stringWithFormat:@"%@
    ",text]];
        [self.textView setText:string];
        //滚动视图
        [self.textView scrollRangeToVisible:range];
    }
    -(void)gcdSaleMethod:(NSString *)name
    {
        while (YES)
        {
            if (_tickets > 0)
            {
                //在主队列都是串行执行的
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSString *info = [NSString stringWithFormat:@"当前票数:%ld,当前线程:%@",_tickets,name];
                    [self appendTextView:info];
                   _tickets--;
                    
                });
                
                if ([name isEqualToString:@"售票GCD-1"])
                {
                    [NSThread sleepForTimeInterval:0.3f];
                }
                else
                {
                    [NSThread sleepForTimeInterval:0.2f];
                }
            }
            else
            {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSString *info = [NSString stringWithFormat:@"已无剩余票数,当前线程:%@",name];
                    [self appendTextView:info];
                });
                break;
            }
        }
    }
    
    @end
        

       接下来对代码中深蓝色部分进行分析:

      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

      这是GCD中的一个用来执行任务的函数实质:把右边的参数(任务)提交给左边的参数(队列)进行执行。采用的是异步的方式。

      案例2:使用GCD模拟售票线程,与案例1稍有不同,案例2中使用了线程组。

     

    #import "ViewController.h"
    
    @interface ViewController ()
    {
        NSInteger _tickets;
    }
    @property (weak, nonatomic) IBOutlet UITextView *textView;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _tickets = 20;
        self.textView.text = @"";
        self.textView.layoutManager.allowsNonContiguousLayout = NO;
        
        //用GCD创建售票线程
        
        /**
         *  队列的名称:队列的标识符
            队列的方式:
            DISPATCH_QUEUE_SERIAL   串行
            DISPATCH_QUEUE_CONCURRENT   并行
         */
        //1.自定义队列
        dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
        //创建线程组
        dispatch_group_t group = dispatch_group_create();
        //2.创建线程组往队列中添加任务
        dispatch_group_async(group, queue,  ^{
            [self gcdSaleMethod:@"售票GCD-1"];
        });
      //调度群组异步任务 dispatch_group_async(group, queue,
    ^{ [self gcdSaleMethod:@"售票GCD-2"]; }); //等线程组中的所有任务完成后,会接收到通知 dispatch_group_notify(group, queue, ^{ dispatch_async(dispatch_get_main_queue(), ^{ [self appendTextView:@"已无剩余票啦!"]; }); }); } -(void)appendTextView:(NSString *)text { NSMutableString *string = [NSMutableString stringWithString:self.textView.text]; NSRange range = NSMakeRange(string.length, 1); [string appendString:[NSString stringWithFormat:@"%@ ",text]]; [self.textView setText:string]; [self.textView scrollRangeToVisible:range]; } -(void)gcdSaleMethod:(NSString *)name { while (YES) { if (_tickets > 0) { //在主队列都是串行执行的 dispatch_async(dispatch_get_main_queue(), ^{ NSString *info = [NSString stringWithFormat:@"当前票数:%ld,当前线程:%@",_tickets,name]; [self appendTextView:info]; _tickets--; }); if ([name isEqualToString:@"售票GCD-1"]) { [NSThread sleepForTimeInterval:0.3f]; } else { [NSThread sleepForTimeInterval:0.2f]; } } else { break; } } } @end

      以上两个案例,实现的都是模拟售票,运行结果如下图:

    八、延时任务的执行

      在软件开发过程中,偶尔会出现,不希望某个线程立马执行,而是在经过一段时间之后再去调度执行,这就会出现任务的延时调度问题。接下来通过3种方法来实现任务的延时执行,在执行3秒之后再输出@“hello world”。

      方式1:使用NSObject的方法

    - (void)viewDidLoad {
        [super viewDidLoad];
        // 延迟执行
        //1.NSObject中的方式
        [self performSelector:@selector(printString:) withObject:@"hello world" afterDelay:3.0];
    }
    -(void)printString:(NSString *)str
    {
        NSLog(@"%@",str);
    }

      方式2:使用GCD的方式

    - (void)viewDidLoad {
        [super viewDidLoad];
        //2.GCD中的方法
        /**
         参数解析
         1.基准时间:程序运行时的时间
         2.偏移时间(单位:纳秒)以当前运行时间为基准在多久之后进行执行
         */
        dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC);
        //将准备输出的字符串添加到队列中去,采用延迟的方式进行等待执行输出
        dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self printString:@"hello world"];
        });
    }
    -(void)printString:(NSString *)str
    {
        NSLog(@"%@",str);
    }

      方式3:使用NSTimer定时器的方式(此处给大家分享使用定时器实现延时任务执行的两种方式)

    - (void)viewDidLoad {
        [super viewDidLoad];
        //使用定时器
        //第一种
        self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(printString) userInfo:nil repeats:YES];
        [self.timer fire];
    }
    -(void)printString
    {
        NSLog(@"hello world");
    }
    - (void)viewDidLoad {
        [super viewDidLoad];
        //使用定时器    
        //第二种
        self.timer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(printString:) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop]addTimer:self.timer forMode:NSDefaultRunLoopMode];    
    }
    -(void)printString
    {
        NSLog(@"hello world");
    }

      在以上两段代码中,有两处蓝色标明的部分,参数repeats,它的作用是:如果参数值为YES,每隔3秒就会输出“hello world”一次。如果参数值为NO,仅输出一次。现在知道了,当repeats的参数值为YES时,会一直执行下去,但又该怎样将它结束掉呢?在NSTimer中给出了停止的方法,看以下代码。

        //停止定时器
        [self.timer invalidate];

     九、仅一次任务执行、多次重复任务执行

      在ios中,给出了仅一次任务执行、多次重复任务执行的方法。废话不多说,直接看代码。

      案例1:仅一次任务执行的方法

    //dispatch_once()函数执行时需要传入一个dispatch_once_t类型(本质就是long型整数)的指针(即predicate参数),该指针用于变量用于判断代码块是否已经执行过。
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
            NSLog(@"====执行代码块===");
            [NSThread sleepForTimeInterval:3.0];
        });

      案例2:多次重复任务执行的方法

    //dispatch_apply()函数将控制提交的代码块重复执行多次,如果该代码块被提交给并发队列,系统可以使用多个线程并发执行同一个代码块。
        //控制代码块执行5次
    dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t time) {
            //time形参代表当前正在执行第几次
            NSLog(@"===执行【%lu】次===%@",time,[NSThread currentThread]);
        });

      经过两天的学习,多线程也告一段落了,现在来总结一下。首先先来看:NSThread、NSOperation、GCD三种多线程技术的流程对比

    •关于多线程必须记住的三个要点
      –只能在主线程中更新UI
      –共享数据争夺的处理
      –不要使用多种多线程技术去争夺同一个资源!
     

  • 相关阅读:
    spring boot 定时任务
    logger日志级别
    jstl与el结合常见用法
    sql 案例
    Python 环境
    java rsa加密解密
    app扫描二维码登陆
    TimerTask定时任务
    spring3+quartz2
    表关系
  • 原文地址:https://www.cnblogs.com/xjf125/p/4859443.html
Copyright © 2011-2022 走看看