美文网首页首页推荐
谈谈对多线程和GCD的理解

谈谈对多线程和GCD的理解

作者: Jadyn_Wu | 来源:发表于2018-03-21 22:42 被阅读46次

    多线程(GCD)

    GCD是异步执行任务的技术之一,通过Dispatch Queue来控制任务的执行,线程管理由系统实现,比以前更加有效率。

    1.多线程编程

    多线程原理

    当mac或者iPhone打开一个app的时候,首先就将包含在应用程序中的CPU命令列配置到内存中,CPU从应用程序指定的地址开始,一个一个地执行CPU命令列。即便地址分散在各处,也是一条无分叉的路径。

    image

    这里的“一个CPU执行的CPU命令列为一条无分叉路径”就是“线程”。

    在1个CPU核工作下的多线程中,这个CPU可以“同时”执行多条不同路径上的命令,实现多线程,但基于CPU一个时间只能执行一个内存块的原则,CPU其实是每隔一段时间在不同路径上切换执行(被称为“上下文切换”),使看起来像同时在执行一样。

    现在一个物理CPU芯片有64核(64个CPU),一台计算机会有多个CPU核同时在工作,这就不是看起来像同时在执行了,可能其他的CPU分担了一些路径执行。

    多线程解决的事情:

    简单来说:多线程可以保证应用程序的响应性能。

    在iOS程序启动时,通过主线程绘制页面、响应触摸事件。如果在主线程中有耗时的操作,就会妨碍主线程(Runloop主循环)的执行,从而不能更新用户界面,画面停滞等问题。

    image

    使用多线程,可以在长时间处理时,仍然能保证用户界面的响应性能。

    多线程容易发生的问题:

    • 多个线程更新相同的资源会导致数据不一致

    • 停止等待时间的线程会导致多个线程相互持续等待(死锁)

    • 使用太多线程会消耗大量内存

    image

    2.GCD原理

    Dispatch Queue执行处理的等待队列。定义想执行的任务并追加到适当的Dispatch Queue中,Dispatch Queue按照追加的顺序(FIFO)执行处理。

    • Serial Dispatch Queue在一条队列上,按照FIFO的顺序执行追加的任务,后追加的任务需要等待前追加的执行完才能执行。

    • Concurrent Dispatch Queue由XNU内核管理多条任务链,不等待且不分顺序的执行任务。

    同步 & 异步

    • 同步(dispatch_sync)将指定任务追加到指定的队列中,并等待这个追加任务的结束。

    • 异步(dispatch_async)将指定任务追加到指定的队列中,不等待这个追加任务的结束,继续向后执行。

    Concurrent Dispatch Queue并发队列原理

    iOS和OS X核心—— XNU内核决定应当使用的线程数,并只生成所需的线程数处理任务,当处理结束时,应当执行的处理数减少时,XNU内核会结束不再需要的线程。假如当Concurrent Dispatch Queue执行8个任务的时候,由于开辟线程会有内存小号,XNU并不一定会开8个线程在8条不同的队列上同时执行8个任务,而可能像下图这样用4个Concurrent Dispatch Queue用线程执行:

    image

    执行顺序会根据处理内容和系统状态发生改变调整。

    Serial Dispatch Queue串行队列原理

    在执行顺序不能改变或不想并发执行任务的时候使用Serial Dispatch Queue,但是Serial Dispatch Queue比Concurrent Dispatch Queue能生成更多的线程,如果有1000个任务就会创建1000个Serial Dispatch Queue,会大量的占用系统的资源。

    1. 同步串行(dispatch_sync + Serial Dispatch Queue)

      不新增线程

      • 在当前串行队列下:线程暂停执行当前队列的任务(当前线程进入无限时间的等待直到新指派的任务完成),新指派的任务根据FIFO法则排在当前队列任务的后面(线程进入无限时间等待前一个任务执行结束),互相等待,引起线程死锁

      • 在新增串行队列下:暂停当前队列中的任务(当前队列进入无限时间的等待直到新指派的任务完成),新指派的任务在新的队列中,线程切换上下文到新队列执行新任务结束后回到原队列(等到结束),原队列线程继续执行原队列任务。

    2. 同步并发(dispatch_sync + Concurrent Dispatch Queue)不新增线程线程暂停执行当前队列的任务,由于并发型队列并行执行数量由内核XNU控制,新任务被派发到新队列中,当前线程切换上下文到新队列执行新派发的任务,执行结束后,切换回原队列。

    3. 异步串行(dispatch_async + Serial Dispatch Queue)新增线程

      • 新增一条队列:新派发的任务被按FIFO的顺序放置在新增的这条队列中,在新增的多条线程中随机选择一条,依次执行队列上的任务,其余线程闲置,直到此队列被释放。

      • 新增多条队列:新派发的任务被各自放置在不同的串行队列中,每个队列对应一条线程,相互执行没有时间顺序,效果等同于并发,但是性能不如并发,因为创建几条队列就会有几条线程数,数量过多时会影响系统性能,而并发是由XNU内核控制并调整线程数的。

    4. 异步并发(dispatch_async + Concurrent Dispatch Queue)新增线程,也新增队列并行执行的处理数量取决于当前的系统状态,即XNU内核基于队列中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定并行的处理数,任务的执行顺序会根据处理内容和系统状态发生改变。

    3.GCD使用

    dispatch_release在iOS6.0\macOS10.8以后就不需要使用了

    3.1 Dispatch Queue创建的dispatch queue对象名称为:dispatch_queue_t

    创建方式:

    //参数1 队列的名称,使用FQDN的命名方式,有助于后期的debug,会出现在程序崩溃时候的crashLog中
    //参数2 串行队列为DISPATCH_QUEUE_SERIAL或NULL  并发队列为DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t queueName = dispatch_queue_create("com.example.gcd.queueName",NULL);
    

    3.2 Main Dispatch Queue && Global Dispatch Queue

    • Main Dispatch Queue:主线程所在的队列,只有一个,属于serial队列,更新UI等处理需要在此队列中执行

    • Global Dispatch Queue:全局队列,属于concurrent队列,有四个优先级

    获取Main Dispatch Queue:

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    

    获取Global Dispatch Queue:

    //global dispatch queue 的四个优先级
    #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
    ​
    //参数1 优先级
    //参数2 备用参数,一般填0
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    组合使用:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     //并发处理一些耗时的任务
     dispatch_async(dispatch_get_main_queue(), ^{
     //回到主线程,做更新UI等操作
     });
    });
    

    3.3 dispatch_set_target_queue

    • 设置队列的优先级

    • 建立队列的执行阶层

    当使用dispatch_queue_create创建队列的时候,不管是串行还是并行,它们的优先级都是DISPATCH_QUEUE_PRIORITY_DEFAULT级别。

    //设置队列的优先级
    //参数1 被设优先级的队列
    //参数2 目标队列
    //将参数1的优先级设置成参数2的优先级
    dispatch_set_target_queue(changedQueue, targetQueue);
    ​
    //设置队列的执行阶层
    //参数1 被指派的队列
    //参数2 目标队列
    //将changedQueue的任务指派到targetQueue中,如果targetQueue是串行,则changedQueue也是串行,反之亦然
    //如果targetQueue是串行可以防止changedQueue并行执行
    dispatch_set_target_queue(changedQueue, targetQueue);
    

    3.4 dispatch_after

    在指定的时间异步追加任务到指定队列,而不是指定时间执行任务。

    //时间单位  NSEC:纳秒 USEC:微秒 SEC:秒 PER:每
    #define NSEC_PER_SEC 1000000000ull  //每秒有多少纳秒
    #define NSEC_PER_MSEC 1000000ull  //每毫秒有多少纳秒
    #define USEC_PER_SEC 1000000ull  //每秒有多少微秒
    #define NSEC_PER_USEC 1000ull  //每微秒有多少纳秒
    ​
    //相对时间
    //参数1:时间起点  DISPATCH_TIME_NOW现在   DISPATCH_TIME_FOREVER永久以后
    //参数2:时间偏移量  ull是C语言的数值字面量  detal的单位是纳秒
    dispatch_time_t time = dispatch_time(dispatch_time_t when, int64_t delta);
    //绝对时间 (如2011年11月11日11时11分11秒)
    //参数1:struct timespec类型的时间
    dispatch_time_t wallTime = dispatch_walltime(const struct timespec * _Nullable when, int64_t delta);
    //在time时间时,将block追加到queue中
    dispatch_after(time, queue, ^{ 
    });
    

    绝对时间的使用方式:

    //将NSDate对象转换成dispatch_time_t对象的方法
    - (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date
    {
     NSTimeInterval interval = date.timeIntervalSince1970;
     double second, subsecond;
     subsecond = modf(interval, &second);
     struct timespec time;
     time.tv_sec = second;
     time.tv_nsec = subsecond * NSEC_PER_SEC;
     dispatch_time_t walltime = dispatch_walltime(&time, 0);
     return walltime;
    }
    

    3.5 dispatch_group

    用来监听多个并发执行任务全部执行结束

    //创建一个group
    dispatch_group_t group = dispatch_group_create();
    //创建全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //添加若干个并发队列执行任务
    dispatch_group_async(group, queue, ^{});
    dispatch_group_async(group, queue, ^{});
    //当所有并发队列执行结束后,触发此方法,将任务追加到指定的队列
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{});
    

    dispatch_group_wait不仅能做到等待group中的并发任务结束,还能设置等待一定的时间。等待的原理就是执行dispatch_group_wait的线程被暂停执行任务,当时间到或者group里的并发任务全部执行完毕,激活dispatch_group_wait的执行线程并返回结果。而dispatch_group_notify是简化dispatch_group_wait使用的一种方式。

    //用dispatch_group_wait实现等待一秒
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    //时间到或者group结束之前,线程被停止在这里,不往下执行。
    long result = dispatch_group_wait(group, time);
    //当时间到了,或者group出结果了,result得到结果
    if(result == 0) {
     //group执行结束
    } else {
     //时间到
    }
    

    3.6 dispatch_barrier_async

    在并发队列中的等待追加的处理全部执行结束后串行追加到queue中,一般用来划分存取数据(排他控制),实现高效率数据库访问。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //任务被最先追加到队列中
    dispatch_async(queue, ^{});
    //执行dispatch_barrier_async的线程开始无限时等待,重新执行的信号是之前的queue中的任务都执行完毕
    dispatch_barrier_async(queue, ^{});//开始执行时,任务被追加到串行队列,后续的任务开始排队
    //在barrier任务执行结束后,后续任务才开始执行
    dispatch_async(queue, ^{});
    

    dispatch_groupdispatch_set_target_queue也能实现按序等待,但是源代码会很复杂。

    3.8 dispatch_apply

    在指定的队列上重复执行一定的次数,并等待全部处理执行结束。

    形式上与dispatch_syncdispatch_group组合效果一样,为了避免阻塞主线程,推荐在dispatch_async中使用。

    dispatch_queue_t globalQueue = dispatch_queue_create("com.example.gcd.queueName",DISPATCH_QUEUE_CONCURRENT);
    //把当前线程喊停,往globalQueue上依次追加10个任务
    dispatch_apply(10, globalQueue, ^(size_t index) {
     NSLog(@"%zu",index);
    });
    //只有等10个任务全部处理结束了,当前线程才会继续往后走,中间这段时间线程阻塞
    NSLog(@"done");
    

    3.9 dispatch_suspend && dispatch_resume

    • dispatch_suspend:挂起已追加但是尚未执行的队列,如果已经开始执行的,就无法挂起了

    • dispatch_resume:恢复执行队列

    //挂起队列
    dispatch_suspend(queue);
    //恢复队列            
    dispatch_resume(queue);
    

    3.10 Dispatch Semaphore

    dispatch_barrier_async处理更细粒度的排他控制,如果在多线程环境下往NSMutableArray中添加Object,,如果两个object几乎同时被加入,那就可能会出现,前一个object插入未完成,后一个就开始了。

    信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。

    信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。

    3.11 dispatch_once

    多线程下百分之百安全的做到只执行一次

    //获取静态变量once
    static dispatch_once_t once;
    dispatch_once(&once, ^{
     //里面的内容可以保证只被执行一次
    });
    

    相关文章

      网友评论

        本文标题:谈谈对多线程和GCD的理解

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