在进行iOS
司机端溃散治理时,发现司机端账号中心里边相关模型比方司机信息模型,里边的多个特点,比方phone_no
、city_id
等多个字段,都存在多线程安全问题,即在主线程更新值,在子线程继续拜访的野指针溃散问题。
由于涉及到的字段相对较多,当时为了确保后边其他的字段也能规避多线程安全问题,决定利用并发行列,来对模型里边的特点,完成读写锁,来确保线程安全。
就相似如下:
然后提测阶段,测试性能压测出现了卡死被watchdog
强杀的溃散。依据溃散日志,检查是在司机信息的location_city_name.getter
这儿卡死导致的。
二. 原因排查
这儿我一向无法理解,按道理并行行列,来完成读写锁,确保get
办法在当前线程履行,而set
办法,会等之前行列里边一切的get
都完成之后堵塞履行完成set
办法,再去履行其他操作。
从理论上来讲,这儿并不存在死锁等候的可能性。
因而我写了一个demo
测试:
Demo如下:
从输出日志能够看出:
这儿只输出了print("--------------Thread VC: (Thread.current)")
而没有输出后边的print("--------------------------------------")
, 也就证明这儿存在死锁,导致后边的使命不会履行。
这是由于都是放在子线程里边进行操作,所以主线程仍然是能够履行,因而UI
事件也是能够呼应的,因而咱们点击,调用点击事件:
// MARK: - Actions
@objc
func buttonTapClicked(button: UIButton) {
self.testModel.update(phoneNo: "100999988888")
}
这是咱们发现主线程也陷入了等候中,从而卡死。
从这个例子里边咱们主要是多次异步拜访了模型里边的phoneNo
特点,而这个特点的运用并行行列的读写锁,来确保phoneNo
拜访的线程安全。
因而咱们进一步简化这个例子:
从简化的例子,咱们仍然能够观察到死锁,由于使命并没有履行结束。
咱们选择了其间的32
线程,检查线程调用的仓库,咱们看到了
2 libdispatch.dylib 0x0000000104cae96c _dispatch_thread_event_wait_slow + 56,
3 libdispatch.dylib 0x0000000104cbfbe8 __DISPATCH_WAIT_FOR_QUEUE__ + 384,
4 libdispatch.dylib 0x0000000104cbf520 _dispatch_sync_f_slow + 184,
这儿_dispatch_thread_event_wait_slow
说明使命正在等候线程来履行,之所以陷入等候,是由于线程池里边的64
条线程悉数被占用了,没有多余的线程来履行使命,那为什么线程池里边的线程会被悉数占用呢。
这儿需求知道一点是,自定义并发行列和大局并发行列都依赖于GCD
线程池的线程去履行使命,并且一切并发行列,最多只能创立64
条线程,假如这64
条线程,悉数被占用,那么并发行列里边的使命,就需求等候线程,直到线程闲暇。
具体详见: GCD的串行行列、并发行列、大局并发行列创立线程数
那为什么并发行列的 64
条都被占用了,并且没有释放呢?
首要咱们看下循环之前的线程分布:
咱们能够看到,在调用大局行列进行读写操作之前,GCD
线程池里边现已存储了2、3、4、5、7、8、9
这几条子线程。
然后咱们看下打印日志的输出:
这儿增加了current thread
的日志打印。
- 首要
for
循环履行1000
个DispatchQueue.global(qos: .default).async
使命,这时分默认优先级大局行列会去GCD
线程池获取线程来履行block
里边的使命,由于GCD
线程池,之前就存在几条线程,因而能够直接获取,去履行block
里边的使命,这几条线程称为第一批。
-
第一批线程履行完打印操作和
driverInfoQueue.sync
的get
操作后,履行到了driverInfoQueue.async(flags: .barrier)
。 -
由于
driverInfoQueue.async(flags: .barrier)
会等到行列前面一切使命履行结束之后,才能履行,而这时分for
循环的DispatchQueue.global(qos: .default).async
的其他线程也现已创立,并履行了打印操作,履行到了driverInfoQueue.sync
这儿。 -
也就是说这时分
driverInfoQueue.async(flags: .barrier)
使命是先进行列的,而后边的driverInfoQueue.sync
是后边进的行列,而这儿的driverInfoQueue.sync
使命又占满了线程池的其他线程,导致线程池64
条线程悉数占满。 -
这时分
driverInfoQueue.async(flags: .barrier)
使命虽然在行列前面,但确一向没有线程来履行只能等候。而后边的driverInfoQueue.sync
使命,由于barrier
的堵塞作用,也一向处于等候中。
三. 解决方案
- 改动加锁方案,针对
DriverInfo
、DriverTokenInfo
、DriverPreferenceInfo
里边的特点比方accessToken
、phone_no
、location_city_id
等多个字段,存在多线程安全问题的字段,运用各自独立的NSLock
锁来解决。