iOS如何高效的使用多线程

  

  

线程是程序执行流的最小单元,一个线程包括:独有ID,程序计数器(程序计数器),寄存器集合,堆栈。同一进程可以有多个线程,它们共享进程的全局变量和堆数据。

  

这里的电脑(程序计数器)指向的是当前的指令地址,通过电脑的更新来运行我们的程序,一个线程同一时刻只能执行一条指令。当然我们知道线程和进程都是虚拟的概念,实际上电脑是CPU核心中的寄存器,它是实际存在的,所以也可以说一个CPU核心同一时刻只能执行一个线程。

  

不管是多处理器设备还是多核设备,开发者往往只需要关心CPU的核心数量,而不需关心它们的物理构成.CPU核心数量是有限的,也就是说一个设备并发执行的线程数量是有限的,当线程数量超过CPU核心数量时,一个CPU核心往往就要处理多个线程,这个行为叫做线程调度。

  

线程调度简单来说就是:一个CPU核心轮流让各个线程分别执行一段时间。当然这中间还包含着复杂的逻辑,后文再来分析。

  


  

  

在移动端开发中,因为系统的复杂性,开发者往往不能期望所有线程都能真正的并发执行,而且开发者也不清楚XNU何时切换内核态线程,何时进行线程调度,所以开发者要经常考虑到线程调度的情况。

  

<强> 1,减少线程切换

  

当线程数量超过CPU核心数量,CPU核心通过线程调度切换用户态线程,意味着有上下文的转换(寄存器数据,栈等),过多的上下文切换会带来资源开销。虽然内核态线程的切换理论上不会是性能负担,开发中还是应该尽量减少线程的切换。

  

<强> 2,线程优先级权衡
  

  

通常来说,线程调度除了轮转法以外,还有优先级调度的方案,在线程调度时,高优先级的线程会更早的执行。有两个概念需要明确:

  
      <李> IO密集型线程:频繁等待的线程,等待的时候会让出时间片。   <李> CPU密集型线程:很少等待的线程,意味着长时间占用着CPU。
      李   
  

特殊场景下,当多个CPU密集型线程霸占了所有CPU资源,而它们的优先级都比较高,而此时优先级较低的IO密集型线程将持续等待,产生线程饿死的现象。当然,为了避免线程饿死,系统会逐步提高被“冷”落线程的优先级,IO密集型线程通常情况下比CPU密集型线程更容易获取到优先级提升。

  

虽然系统会自动做这些事情,但是这总归会造成时间等待,可能会影响用户体验。所以笔者认为开发者需要从两个方面权衡优先级问题:

  
      <李>让IO密集型线程优先级高于CPU密集型线程。   <李>让紧急的任务拥有更高的优先级。   
  

比如一个场景:大量的图片异步解压的任务,解压的图片不需要立即反馈给用户,同时又有大量的异步查询磁盘缓存的任务,而查询磁盘缓存任务完成过后需要反馈给用户。

  

图片解压属于CPU密集型线程,查询磁盘缓存属于IO密集型线程,而后者需要反馈给用户更加紧急,所以应该让图片解压线程的优先级低一点,查询磁盘缓存的线程优先级高一点。

  

值得注意的是,这里是说大量的异步任务,意味着CPU很有可能满负荷运算,若CPU资源绰绰有余的情况下就没那个必要去处理优先级问题。

  

<强> 3,主线程任务的优化

  

有些业务只能写在主线程,比如UI类组件的初始化及其布局。其实这方面的优化就比较多了,业界所说的性能优化大部分都是为了减轻主线程的压力,似乎有些偏离了多线程优化的范畴了、下面就基于主线程任务的管理大致罗列几点吧:

  
      <李>内存复用
      李   
  

通过内存复用来减少开辟内存的时间消耗,这在系统UI类组件中应用广泛,比如UITableViewCell的复用。同时,减少开辟内存意味着减少了内存释放,同样能节约CPU资源。

  
      <李>懒加载任务
      李   
  

既然UI组件必须在主线程初始化,那么就需要用时再初始化吧,迅速的写时复制也是类似的思路。

  
      <李>任务拆分排队执行
      李   
  

通过监听Runloop即将结束等通知,将大量的任务拆分开来,在每次Runloop循环周期执行少量任务。其实在实践这种优化思路之前,应该想想能不能将任务放到异步线程,而不是用这种比较极端的优化手段。

  
      <李>主线程空闲时执行任务李   
     //这里是主线程上下文      设置(dispatch_get_main_queue ()、^ {//等到主线程空闲执行该任务      });

iOS如何高效的使用多线程