多线程

2021-02-02  本文已影响0人  生产八哥

线程

queue是队列,thread是线程。不开启新线程就是当前queue无效,任务的执行还是在这个方法所在的线程里不变。还有主队列并不特殊,只是它的主线程创建时机是在main()之前,由系统早就创建好了,不会像自定义串行队列一样在使用到的异步执行的时候才去创建。所以异步执行+串行子队列会开启一个线程,但异步执行+主线程就不会开启新的,因为早就开好了主线程。

微内核Mach 封装宏内核 BSD 为了更好地利用多核,加入了工作队列,以支持多核多线程处理,这也是 GCD 能更高效工作的基础

GCD底层就是copy和封装要执行的任务block,设置回调函数func,因为队列也是对象,所以这里会递归执行到根类,将block dx_push,用pthread_create创建线程,然后dx_invoke执行block。

总结: queue队列也是个对象,有父类,根类,也需要alloc init,且传入的serial或concurrent就决定了它初始化时候宽度为1还是Max。它的isa指向的OS_dispatch##_queue_serial是宏定义拼接而成的.创建队列在底层的实现是通过模板创建的。


dispatch_barrier

dispatch_sync的底层其实就是GCD的栅栏化barrier。且dispatch_barrier在串行队列上发挥不了作用。栅栏函数只对自定义的并发队列有效,串行队列本身底层就是栅栏,所以等于脱裤子放屁,而且更耗性能。如果栅栏函数对全局并发队列,则可能会崩溃,因为全局并发队列不止是自己在调用,系统也在调用,如果barrier阻塞了系统函数,就会异常,即如果对全局并发队列执行dispat_barrier_sync,会崩溃。如果对全局并发队列执行dispat_barrier_async,则无效,起不到阻塞作用,需要是自定义的并发队列。


NSThread

NSThread 是苹果官方提供的,可以直接操作线程对象,控制start阻塞sleepUntilDate强制停止exit。当调用[thread start];后,系统把线程对象放入可调度线程池中,线程对象进入就绪状态。需要自己维护线程的生命周期(主要是创建)、线程之间同步等。

NSOperation

NSOperation、NSOperationQueue 是基于 GCD 更高一层的封装,完全面向对象。可以方便的控制线程,比如取消线程、暂停线程、设置线程的优先级、设置线程的依赖,所以多用于下载库的实现。

NSOperation的子类NSBlockOperationNSInvocationOperation可以实现不同的回调方式。NSOperationQueue的优势是

GCD

GCD 是 Apple 开发的一个多核编程的较新的解决方法。GCD的优势是

应用
dispatch_once
dispatch_barrier_async
dispatch_after
dispatch_group_notify
dispatch_semaphore
死锁

死锁:系统发现此线程既需要等待又需要执行,不知道怎么执行,所以抛出了_crash异常。

GCD 死锁的充分条件是:“向当前队列重复同步提交 block”。提交的 block 阻塞了队列,而队列阻塞后永远无法执行完dispatch_sync(),可见这里完全和代码所在的线程无关

数据竞争Data race

Data Race是指多个线程在没有正确加锁的情况下,同时访问同一块数据,并且至少有一个线程是写操作,对数据的读取和修改产生了竞争,从而导致各种不可预计的问题。
可以在xcode的scheme的 Diagnostics页面, 选中Thread Sanitizer,来更好的追踪此类问题。
如何解决这种Data race问题呢? 将共享变量的 read和write放在同一个DispatchQueue中. 采用什么样的DispatchQueue, 这里有2种方法,推荐第二种:

  1. 采用串行的DispatchQueue, 所有的read/write都是串行的, 所以不会出现Data race的问题; 但是效率比较低,即使所有的操作都是read, 也必须排队一个一个的读.
  2. 采用并行的DispatchQueue, 所有的read都可以并行进行, 所有的write都必须"独占"(barrier)的进行: 我write的时候, 任何人不允许read或者write.

自定义线程池

为什么要构建线程池?
答:大量的任务提交到后台队列时,某些任务会因为某些原因被锁住导致线程休眠,或者被阻塞,concurrent queue 随后会创建新的线程来执行其他任务。当这种情况变多时,或者 App 中使用了大量 concurrent queue 来执行较多任务时,App 在同一时刻就会存在几十个线程同时运行、创建、销毁。CPU 是用时间片轮转来实现线程并发的,尽管 concurrent queue 能控制线程的优先级,但当大量线程同时创建运行销毁时,这些操作仍然会挤占掉主线程的 CPU 资源。使用 concurrent queue 时不可避免会遇到这种问题,但使用 serial queue 又不能充分利用多核 CPU 的资源。工具 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。我把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。

那么构建一个线程池,需要3个参数:名称、线程数量、优先级
每种优先级各对应n个队列。获取队列queue的时候就从创建好的这n个队列轮询返回给用户使用,一般n定位5个左右即可,视使用场景而定。

为什么要使用队列数组(每个队列是同步队列)来实现队列池呢?

上一篇下一篇

猜你喜欢

热点阅读