美文网首页
Objective-C 线程篇(二): 大中枢派发(Grand

Objective-C 线程篇(二): 大中枢派发(Grand

作者: Tenloy | 来源:发表于2018-10-29 10:42 被阅读21次

    什么是GCD?

    Grand Central Dispatch(GCD)是异步执行任务的技术之一,用非常简洁的技术方法,实现了极为复杂繁琐的多线程编程

    什么是多线程编程?

    先来复习一下操作系统中线程相关的知识点:
    首先,代码是怎么运行的?

    • 源代码通过编译器转换为CPU命令列(二进制编码),应用程序就是CPU命令列和数据的汇集,在应用程序启动后,首先便将包含在应用程序中的CPU命令列配置在内存中。
    • CPU从应用程序指定的地址开始,一个一个的执行CUP命令列。在OC的if语句和for语句等控制语句或函数调用的情况下,执行命令列的地址会远离当前的位置(位置迁移),但是由于一个CUP一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。

    一个CPU执行的CPU命令列尾一条无分叉的路径即为“线程”

    上下文切换

    OS X和ios的核心XNU内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路经。执行中路经的状态,例如CPU的寄存器的信息保存到各自路经专用的内存块中,从切换目标路经专用的内存块中,复原CPU寄存器的信息,继续执行切换路经的CPU命令列,这被称为“上下文切换”

    上下文切换是并行(单处理器中进程被交替执行,表现出并发外部特征)`的核心关键。

    单核中的多线程是并发,其实是顺序执行的,只不过CPU高速的切换,表面看起来像是并行。
    多核中的多线程,在线程数小于<CPU核数时,是真正的并行。
    关于并发和并行的区别,可以看上一篇中的介绍

    iOS和OS X的核心 — XNU内核决定应当使用的线程数,并只生成所需的线程执行处理,另外,当处理结束,应当执行的处理数减少时,XNU内核会结束不再需要的线程,XNU内核仅使用并行队列便可完美的管理并行执行处理的线程

    多线程编程的缺点:
    是易发生各种问题,比如:数据竞争、死锁,而且使用太多线程会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。
    优点:
    保证应用程序的响应性能

    主线程
    应用程序启动时。通过最先执行的线程,即‘主线程’来描绘用户界面、处理触摸屏幕的事件,如果在该线程中进行长时间的处理,会造成主线程阻塞,会妨碍主线程中被称为RunLoop的主循环执行,从而导致不能更新用户界面、应用程序画面长时间停滞等问题

    GCD大大简化了偏于复杂的多线程编程的源代码,与Block结合使用,只需要将要执行的任务并追加到适当的Dispatch Queue

    GCD的API

    # Dispatch Queue(调度队列)

    • Dispatch Queue按照追加的顺序(先进先出FIFO)执行处理
    • Dispatch Queue分两种
      一种是等待现在执行中处理结束的 Serial Dispatch Queue
      一种是不等待现在执行中处理结束的Concurrent Dispatch Queue

    # dispatch_queue_create(创建队列)

    1个并行队列 + 多个异步任务(dispatch_async) = 会开启多线程
    多个(1个串行队列+1个(同步/异步)任务(dispatch_sync)) = 多线程

    /**
     * @参数1 指定Dispatch Queue名称(推荐使用应用程序ID这种逆序全程域名,该名称便于Xcode和Instruments调试,会出现在CrashLog中)
     * @参数2 Serial Dispatch Queue指定为NULL;Concurrent Dispatch Queue指定为DISPATCH_QUEUE_CONCURRENT
     * @return 为表示Dispatch Queue的"dispatch_queue_t类型"
     */
    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create ("com.example.MySerialDispatchQueue" , NULL);
    
    dispatch_async(queue, ^{
    });
    
    dispatch_release(mySerialDispatchQueue)
    
    - dispatch_queue_t类型变量 ,必须程序员自己负责释放,像OC的引用计数式内存管理一样,需要通过`dispatch_retain`函数和`dispatch_release`函数的引用计数来管理内存
    - 在`dispatch_async、diapatch_sync`函数中追加Block到Dispatch Queue(该Block通过dispatch_retain函数持有Dispatch Queue)
    - 一旦Block执行结束,就要通过dispatch_release函数函数释放该Block持有的Dispatch Queue
    
    "释放时机"
    - 在dispatch_async函数中追加Block到Dispatch Queue后,即是立刻释放Dispatch Queue,该Dispatch Queue由于被Block持有也不会废弃,因而Block能够执行,Block执行结束后释放该Block持有的Dispatch Queue,这时谁都不持有Dispatch Queue,因此它被废弃
    - 在通过函数或方法名获取Dispatch Queue以及其他名称中包含‘create’的API生成的对象时,有必要通过dispatch_retain函数持有,并在不需要时通过dispatch_release函数释放
    
    "系统对于一个串行队列,就只生成并使用一个线程,所以串行队列的生成个数应当仅限所必需的数量,不能大量生成。
    对于并行队列,不管生成多少,由于XNU内核**只使用有效管理的线程**,不会出现串行队列那种问题。"
    

    # Main Dispatch Queue / Global Dispatch Queue(内置队列)

    ## Main Dispatch Queue(串行队列)

    追加到Main Dispatch Queue中的处理在主线程的RunLoop中执行

    ## Global Dispatch Queue(并行队列)

    Global Dispatch Queue有4个执行优先级

    • 最高优先级(High Priority)
    • 默认优先级(Default Priority)
    • 低优先级(Low Priority)
    • 后台优先级(Background Priority)
    #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
    
    dispatch_get_global_queue(优先级变量, unsigned long flags)
    
    Global Dispatch Quene有如下8种:
     Global Dispatch Quene(High Priority)
     Global Dispatch Quene(Default Priority)
     Global Dispatch Quene(Low Priority)
     Global Dispatch Quene(Background Priority)
     Global Dispatch Quene(High Overcommit Priority)
     Global Dispatch Quene(Default Overcommit Priority)
     Global Dispatch Quene(Low Overcommit Priority)
     Global Dispatch Quene(Background Overcommit Priority)
    
    优先级中附有Overcommit的Global Dispatch Quene使用在Serial Dispatch Quene中。
    "不管系统状态如何,都会强制生成线程的Dispatch Quene。所以这也是不要大量生成串行队列的原因。对于并行队列,不管生成多少,由于XNU内核**只使用有效管理的线程**,不会出现大量创建线程的状况"
    

    同XNU内核用于Global Dispatch Queue的线程并不能保证实时性,所以优先级只是个大致判断

    XNU内核管理,会将各自使用的队列的执行优先级,作为线程的执行优先级使用,所以添加任务时,需要选择与处理的任务对应优先级的队列

    对上面两种队列执行dispatch_retain函数和dispatch_release函数无效,开发者无需关心这两者的保留、释放

    # dispatch_set_target_queue

    dispatch_queue_create函数生成的队列,生成的线程优先级 = Global Dispatch Queue的默认优先级
    变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数
    
    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue",NULL);
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_PRIORITY_BACKGROUND ,0);
    /*
     * 指定要变更执行优先级的Dispatch Queue为dispatch_set_target_queue函数的第一个参数
     * 指定与要使用的执行优先级相同优先级的Dispatch Queue为第二个参数(目标)
     * Main Dispatch Queue和Global Dispatch Queue不可指定为第一个参数
     */
    dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
    
    "用途:"
    - 变更执行优先级
    - 目标队列会变成第一个参数队列中任务的执行阶层
        多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理(可防止Serial Dispatch Queue处理并行执行)
        使多个serial队列变并行为串行
    

    # dispatch_after(延迟执行)

    /*
    获取精确时间点
    typedef uint64_t dispatch_time_t;
    #define DISPATCH_TIME_NOW (0ull)
    #define DISPATCH_TIME_FOREVER (~0ull)
    * 参数1:  开始时间  DISPATCH_TIME_NOW(现在的时间)
    * 参数2:多久后  数值和NSEC_PER_SEC的乘积得到单位为毫微秒的数值,ull是C语言的数值字面量,是显示表明类型时使用的字符串(表示‘unsigned long long’) ,NSEC_PER_MSEC表示毫秒单位
    */
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW , 3ull * NSEC_PER_SEC);
    /*
     * 参数1:指定时间用的dispatch_time_t类型的值,dispatch_time_t类型的值使用dispatch_time函数或者dispatch _walltime函数生成
     * 参数2:指定要追加处理的Dispatch Queue
     * 参数3:指定记述要执行处理的Block
     */
    dispatch_after(time , dispatch_get_main_queue(), ^{
      NSLog(@"waited at least three seconds ");
    });
    
    
    注意:
      1. dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue,上述代码与3秒后用dispatch_async函数追加Block到Main Dispatch Queue的相同
      2. 因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟是,这个时间会更长
    
    
    dispatch_walltime函数由POSLX中使用的struct timespec类型的时间得到dispatch _time_t类型的值
    dispatch _time函数通常用于计算相对时间
    dispatch_walltime函数用于计算绝对时间,需要指定精确时间参数,可作为粗略的闹钟功能使用
    

    # Dispatch Group

    指定:追加到Dispatch Queue中的多个处理全部结束后,执行某种操作
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{ NSLog(@"blk1");});
    dispatch_group_async(group, queue, ^{ NSLog(@"blk2");});
    
    /*
     * 参数1:要监视的Dispatch Group。
     * 参数2 3:在追加到该Dispatch Group中的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中
     */
    "dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});"
    或者
    /*
      使用dispatch_group_wait函数仅等待全部执行结束,不设置结束时通知
      * 参数2: 指定等待时间 dispatch_time_t类型的值 (DISPATCH_TIME_FOREVER 一直)
      * return  如果返回值不为0,表示经过等待,任务还在执行中,如果为0,全部执行结束
     */
    "dispatch_group_wait(group, timeout);"
    dispatch_release(group);
    
    1. Dispatch Group
          无论是串行还是并行队列,Dispatch Group都可监视这些处理执行的结束。一旦检测到所有的处理执行结束,就可将结束的处理追加到Dispatch Queue中
    2. dispatch_group_create
          使用"dispatch_group_create"函数生成dispatch_group_t类型的Dispatch Group。
          因为函数名中含有create,所以在使用结束后需要通过"dispatch_release"函数释放
    3. dispatch_group_async
          dispatch_group_async函数追加Block到指定的Dispatch Queue中,与dispatch_async函数不同的是指定生成的Dispatch Group为第一个参数,指定的Block属于指定的Dispatch Group
    4. dispatch_release
          与追加Block到Dispatch Queue时同样,"Block通过dispatch_retain函数持有Dispatch Group",从而使得该Block属于Dispatch Group,这样如果Block执行结束,该Block就通过dispatch_release函数释放持有的Dispatch Group
          一旦Dispatch Group使用结束,不用考虑属于该Dispatch Group的Block,立即通过dispatch_release函数释放即可
    
    5. dispatch_group_notify
          在追加到Dispatch Group中的处理全部执行结束时,dispatch_group_notify函数会将执行的Block追加到Dispatch Queue中
          在dispatch_group_notify函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行结束
    
    

    # dispatch_barrier_async(变无序为有序)

    dispatch_barrier_async 函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用
    
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.Forbarrier",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, blk1);
    dispatch_barrier_async(queue, blk_barrier);
    dispatch_async(queue, blk2);
    
    dispatch_release(queue);
    
    ① 执行dispatch_barrier_async之前的任务
    ② 执行dispatch_barrier_async函数添加的任务blk_barrier block
    ③队列恢复一般动作,追加到Concurrent Dispatch Queue的处理又开始并行执行
    

    # dispatch_apply

    dispatch_apply函数"按指定的次数"将指定的Block追加到指定的队列中,并等待全部处理执行结束
    
    /*
     * 参数1:重复次数
     * 参数2:执行队列
     * 参数3:任务
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY_DEFAULT,0);
    dispatch_apply(10, queue, ^(size_t index){
       NSLog(@"%zu",index);
    });
    
    1. Global Dispatch Queue中执行,所以各个处理的执行时间不定,但是输出结果的最后必定是done,这是因为dispatch_apply函数或等待全部处理执行结束
    2. dispatch_apply和dispatch_sync函数一样,会等待处理执行结束,因此推荐在dispatch_async函数中非同步的执行dispatch_apply函数
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY_DEFAULT,0);
    
    //在Global Dispatch Queue中非同步执行
    dispatch_async(queue, ^{
        //Global Dispatch Queue,等待dispatch_apply函数中全部处理执行结束
        dispatch_apply([array count], queue, ^(size_t index){   
           //并列处理包含在NSArray对象的全部对象  index为0-10
           NSLog(@"%zu :%@",index,[array objectAtIndex:index]);
        });
        //dispatch_apply函数中的处理全部执行结束
       //在Main Dispatch Queue中非同步执行
       dispatch_async(dispatch_get_main_queue(),^{
         //在Main Dispatch Queue中执行处理
         NSLog(@"done");
      });
    });
    

    # dispatch_suspend/dispatch_resume(队列挂起与恢复)

    //dispatch_suspend函数挂起指定的Dispatch Queue
    dispatch_suspend(queue)
    //dispatch_suspend函数恢复指定的Dispatch Queue
    dispatch_resume(queue)
    
    这些函数"对已经执行的处理没有影响"
      挂起后,追加到Dispatch Queue中但尚未执行的处理,在此之后停止执行
      恢复后使得这些处理能继续执行
    

    # Dispatch Semaphore

    Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号,所谓信号,类似过马路时常用的手旗,可以通过时举起手旗,不可以通过时放下手旗
    在Dispatch Semaphore中,使用计数来实现该功能,"计数为0时等待,计数为1或大于1时,减去1而不等待"
    
    /* 
     * 使用dispatch_semaphore_create函数生成Dispatch Semaphore
     * 参数表示计数的初始值,本例中为1
     * 函数名称中包含create,必须自己通过dispatch_release函数释放,和dispatch_retain函数持有
     */
    dispatch_semaphore_t  semaphore = dispatch_semaphore_create(1);
    
    /*
     * dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大于或等于1
     * 当计数大于等于1,或者待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回
     * 参数2:等待时间 dispatch_time_t类型
     * return 返回值与dispatch_group_wait函数相同, 0表示执行完,1 表示正在执行,计数为0,继续等待
     */
    dispatch_semaphore_wait  (semaphore, DISPATCH_TIME_FOREVER);
    
    // 使计数+1,如果有wait等待的任务,执行
    dispatch_semaphore_signal(semaphore);
    
    //释放 
    dispatch_release(semaphore);
    

    # dispatch_once

    dispatch_once函数时保证在应用程序执行中只执行一次指定处理的API,在多线程环境下保证安全

    # Dispatch I/O与Dispatch Data对象

    通过Dispatch I/O读写文件,使用Global Dispatch Queue将一个文件按大小read/write。提升读取、写入速度
    
    //创建队列
    pipe_q = dispatch_queue_create("PipeQ",NULL);  
    //创建Dispatch I/O对象
    pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM,fd,pipe_q,^(int err){
       close(fd);   //发生错误时执行
    });
    
    *out_fd = fdpair[i];
    //设置一次读取的最大字节
    dispatch_io_set_high_water(pipe_channel, SIZE_MIN);
    //设置一次读取的最小字节
    dispatch_io_set_low_water(pipe_channel,SIZE_MAX);
     
    
    //开始异步读取
    dispatch_io_read(pipe_channel,0,SIZE_MAX,pipe_q, ^(bool done,dispatch_data_t pipe data,int err){
    //每当各个分割的文件快读取结束时,将含有文件块数据的Dispatch Data传递给dispatch_io_read函数指定的读取结束时回调用的Block。回调用的Block分析传递过来的Dispatch Data并进行结合处理
       if(err == 0)
         {
           size_t len = dispatch_data_get_size(pipe data);
           if(len > 0)
           {
              const char *bytes = NULL;
              char *encoded;
    
              dispatch_data_t md = dispatch_data_create_map(pipe data,(const void **)&bytes,&len);
              asl_set((aslmsg)merged_msg,ASL_KEY_AUX_DATA,encoded);
              free(encoded);
              _asl_send_message(NULL,merged_msg,-1,NULL);
              asl_msg_release(merged_msg);
              dispatch_release(md);
           }
          }
    
          if(done)
          {
             dispatch_semaphore_signal(sem);
             dispatch_release(pipe_channel);
             dispatch_release(pipe_q);
          }
    });
    
    

    相关文章

      网友评论

          本文标题:Objective-C 线程篇(二): 大中枢派发(Grand

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