GCD
2021-02-28 本文已影响0人
HughKaun
背景
客户端内绝大部分上都是用苹果提供的GCD做异步任务,还有小部分是直接用NSThread或者pthread或者是NSOperation
客户端内GCD使用场景
类别 | 场景 | 队列类型 | 优先级 | 重要性 |
---|---|---|---|---|
子线程IO | 读取本地文件、图片缓存 | 串行 | 低 | 中 |
网络请求 | 并行 | 中 | 中 | |
数据库操作 | 串行 | 低 | 高 | |
旧日志系统所有日志记录(新日志用pthread) | 并行 | 低 | 高 | |
UserDefault | 并行 | 低 | 高 | |
模拟互斥 | 串行队列顺序访问共享资源 | 串行 | 低 | 中 |
并行队列模拟读写锁 | 并行 | 低 | 中 | |
异步转同步 | 异步任务转同步任务执行 | 并行 | 低 | 低 |
子线程计算 | 网络回调数据处理 | 并行 | 中 | 高 |
一般数据处理 | 并行 | 中 | 低 | |
客户端内所有异步绘制过程 | 并行 | 高 | 高 | |
Feed,文章等富文本异步计算布局 | 并行 | 中 | 高 | |
子线程视频编解码 | 并行 | 低 | 低 | |
消息箱处理socket消息 | 并行 | 中 | 低 |
目前遇到的痛点
-
线程爆炸
- 在没有空闲线程情况下,
dispatch_async
会创建新线程,造成线程过多 - 某些循环里会频繁调用GCD去异步执行任务,会瞬间创建很多线程
- 串行队列在底层是overcommit的,理论在没有空闲线程时,只要是async操作都会创建新线程,如果有很多创建的串行队列,可能会造成线程爆炸
- 模拟器测试,串行队列最多可以同时创建512个线程,并行队列最多可以同时创建64个线程
- 在没有空闲线程情况下,
-
造成死锁
-
dispatch_sync
会阻塞当前线程,处理不当会造成死锁 - GCD的线程池有数量限制,任务密集时会创建不出线程,造成死锁
-
解决方案
- 在GCD任务层级加入数量限制,限制可以异步的任务数量
- 引入协程
- 加入GCD队列池,限制队列创建,增加队列复用,减少线程数
- 修改WBAPM,支持检测长时间执行的block
详细技术方案
-
GCD机制修改方案
1.1 创建队列
- hook
dispatch_queue_create
->wb_dispatch_queue_create
- 对于创建串行队列请求,设置最大队列阈值X (X的值和设备有关,需要进一步测试得出)
- Y<X,创建标准的isolate串行队列,更新isolate队列计数Y+1
- Y>=X,新创建的队列会自动target到公用队列
- 公用队列有一个队列池控制,最多可能有N个队列
- Y>N,会在N个公用队列中取当前压力最小的一个
- 监控队列池中每个队列是否销毁,并更新Y-1
- 自动为每个队列附带一个specific_value,解决之后同队列sync死锁问题
- 对于创建并行队列,扩展参数枚举值,增加业务相关枚举
- 对于创建的LOW,DEFAULT,HIGH 优先级的并行队列,直接返回Default_Global_Queue
- 对于传入的业务相关枚举,根据本地配置,透明的返回对应优先级Global_Queue
- AyncDraw -> High
- IO -> Low
1.2 同步操作sync
- hook
dispatch_sync
->wb_dispatch_sync
- 判断当前context队列和sync到的队列是否是一个对列,如果是一个队列,直接执行block,不是一个在调用sync
1.3. 异步操作async
- 串行队列的async,由于有上面队列池限制,不会线程爆炸
- 并行队列async
- 在任务层级添加二级队列,可以设置最大任务阈值,超过阈值任务会在二级队列等待
- 监控在短时间X内,对相同block的async操作,动态阻塞后续async,并在二级队列等待,另外会记录日志。
- hook
hook原dispatch_xxx -> wb_dispatch_xxx

当前串行队列和并行队列原理

修改后队列池原理

- 尝试引入coobjc协程库(阿里)
- WBAPM新增BlockTimeRecorder,记录长时间没执行完的block
具体实现
WBGCDQueueGuard
-
wb_dispatch_create_queue
->dispatch_create_queue
替换创建队列api -
wb_dispatch_set_target_queue
->dispatch_set_target_queue
替换设置target api -
wb_dispatch_async1
->dispatch_async
替换异步dispatch api -
wb_dispatch_sync1
->dispatch_sync
替换同步dispatch api
WBGCDGuardAsyncBucket
- 并行队列二级等待Bucket
- 遵循标准生产者消费者模型,dispatch_async相当于生产者,有一个线程充当消费者
- 消费者最多只能同时执行N个任务
- 支持简单负载均衡,比如一个循环,同一个任务短时间多次dispatch_async,会分时执行
WBGCDRootQueue
- 管理公共串行队列
- 管理公共串行队列的状态,包括被target的数量,公共串行队列的当前压力状态
- 每当创建串行队列时,对调用
candidate_root_queue
接口获取队列
WBGCDGuardConfiguration
- 管理GCD Guard的配置
- 管理AB状态
- 输出一个全局的config
WBGCDGuard_Private
- 私有全局变量声明
- 私有全局工具方法
私有全局工具方法
/**
返回queue的类型
@param 队列
@return kQueueIsSerial: 串行队列;kQueueIsConcurrent:并行队列
*/
int wb_queue_type(dispatch_queue_t queue);
/**
返回queue是否已经加入过任务
@param 队列
@return 是否
*/
BOOL wb_queue_has_post_task(dispatch_queue_t queue);
/**
根据QOS返回对应的根队列池
@param qos_class_t
@return RootSerialQueuePool
*/
RootSerialQueuePool &wb_root_serial_queue_pool(qos_class_t qos);
/**
返回queue所对应的根队列
@param 队列
@return RootSerialQueue
*/
RootSerialQueue *wb_root_serial_queue_with_queue(dispatch_queue_t queue);
/**
返回queue是否是全局队列
*/
BOOL wb_queue_is_global_queue(dispatch_queue_t queue);
/**
返回queue是否是根队列
*/
BOOL wb_queue_is_root_serial_queue(dispatch_queue_t queue);
/**
返回queue是否是以根队列为target
*/
BOOL wb_queue_is_decendant_from_root_queue(dispatch_queue_t queue);
/**
返回queue的target queue 98
*/
dispatch_queue_t wb_queue_target_queue(dispatch_queue_t queue);
dispatch_queue_t wb_dispatch_queue_get_ancestor_target_queue(dispatch_queue_t queue, wb_queue_ancest
or_block block);
/**
返回queue的根队列
*/
dispatch_queue_t wb_dispatch_queue_get_ancestor_root_target_queue(dispatch_queue_t queue);
风险及应对
- 相同target_queue的队列之前的死锁问题,通过设置specific info解决
- 队列复用池数量制定,可以通过大量任务压力测试得出一个阈值
- 主线程sync操作可能需要等更久,扫描所有可能的主线程sync,改成用锁机制
- 异步任务可能变慢,没有影响,异步任务本来就需要等待