使用Thread Sanitizer(TSan)调试iOS Data Race EXC_BAD_ACCESS错误
如果您是iOS开发人员,则EXC_BAD_ACCESS
会遇到EXC_BAD_ACCESS
崩溃。 如果您已经尝试启用NSZombie
而没有任何运气(因此排除了内存问题/指针悬空),则可能的原因可能是代码中的数据争用情况。 这篇文章讨论了什么是数据争用条件,以及如何使用Thread Sanitizer调试问题。
什么是数据竞争条件?
当多个线程在不同步的情况下访问同一内存位置时发生数据争用情况,并且这些访问中的至少一项是写操作。
众所周知,数据争用条件很难调试,因为它们与时间有关,并且不会在每次运行代码时发生。 进入—线程消毒剂。
什么是线程消毒剂?
Thread Sanitizer(或TSan)是LLVM工具系列的一部分,该工具结合了编译时检测和运行时监视以检测代码中的线程错误。 它是在Xcode 8中引入的,并支持Objective C和Swift。 它可以检测多种类型的线程错误,其中之一是数据争用条件。
在启用TSan的情况下运行–
a)点击申请方案,然后点击“编辑方案”
b)在“诊断”选项卡下的“运行时清理程序”部分中,启用“线程清理程序”复选框。 您也可以通过选中“问题暂停”复选框来选择在检测到数据争用时暂停程序执行。 (注意-“ Thread Sanitizer”选项仅在您在iOS Simulator上运行时才可用。在设备上不支持)
c)运行您的应用程序并执行导致EXC_BAD_ACCESS
崩溃的步骤。
如果您的应用程序具有数据争用条件,则TSan将检测到它并将其记录到控制台。 (请注意,TSan可能不会在一次尝试中检测到所有数据争用条件。在这种情况下,请尝试执行重复多次的步骤)。
解释TSan日志–
数据争用条件的TSan日志如下所示(重要部分以红色突出显示)–
- TSan日志包含有关哪些确切线程参与了数据竞争条件的信息。 在上面的示例中,
main thread
和thread T7
参与了数据竞争条件。 - 日志包含每个参与线程的堆栈跟踪。 记录的堆栈跟踪中的星号(*)指向访问/更改数据的确切代码。 在上面的日志中,设置/读取
someBool
变量时发生了数据争用。 另外,如果您在调用堆栈中上updateBoolValue()
一级,您将获得访问/updateBoolValue()
问题的内存位置的确切方法(startConsumer()
和updateBoolValue()
) - 该日志还告诉您所涉及的内存位置是否存在于程序的堆/堆栈/数据段中,以及哪个线程分配了该内存。 在上面的示例中,TSan日志状态为-
Location is heap block of size 880 at 0x7b5c00000380 allocated by main thread
。
请注意— TSan日志最多包含4个调用堆栈,这些堆栈访问了有问题的内存位置。 这是因为TSan旨在仅跟踪多达4种不同的访问。
现在您已经找到了数据争用条件,让我们看看如何解决它。
解决数据争用条件–
数据争用条件的修复很大程度上取决于代码的设计方式。 没有解决此问题的“正确/标准”方法。 这意味着只有您会知道正确的修补程序。
广义上讲,有两种方法可以解决数据争用条件–
- 防止多个线程访问相同的内存位置。 为此,您可以-选择使用GCD函数(
DispatchQueue.sync()
/DispatchQueue,async()
)将所有访问(读/写)分配到同一队列DispatchQueue,async()
或者在不同线程之间传递对象时复制对象。 - 通过使用Apple提供的同步原语
@synchronized()
例如@synchronized()
,NSLock
等@synchronized()
,同步访问多个线程使用的内存位置。
总结(关于TSan的几个关键点)–
- 即使TSan在特定的应用程序运行中没有表现出来,它也可以检测到数据竞争状况。
- TSan可能在单个应用程序运行中未检测到数据争用条件。 如果一个内存位置被四个以上的线程访问,则尤其如此。
- TSan要求重新编译代码,因此无法在预构建的二进制文件上使用。
- 在启用TSan检查的情况下运行代码可能导致CPU速度降低2%至20⨉,并使内存使用量增加5%至10⨉。
- TSan仅与iOS模拟器一起使用,并且在真实的iOS设备上运行时无法启用。
希望本文能帮助您调试/修复代码中的数据竞争条件。 总而言之,我们讨论了什么是数据竞争条件,如何使用TSan调试此条件以及代码中解决此问题的一些可能方法。
编码愉快!
我目前正在Pune的Raja Software Labs研究iOS中的一些很棒的东西。