首页 > 编程语言 > 详细

如何知道一个锁到底被哪个线程占用?

时间:2020-12-27 02:10:32      阅读:72      评论:0      收藏:0      [点我收藏+]
一、问题背景

在多线程环境下调试或定位问题时,有时我们会发现某重要线程被卡住在等待某个锁上,但具体是哪个线程或哪段代码拿了锁(特别是代码逻辑比较复杂、线程较多的情况下)又无法一下看出来。

这时我们就需要在调试器里把所有线程点一遍,看他们当前的调用栈,然后再对照源代码看各线程的调用栈上有没有哪个方法是加了这把锁的。

在源码不太熟的情况下,这是个非常费时费力的工作。

二、解决思路

参考上一篇《关于 dispatch_semaphore 优先级反转》里讲的,对于某几种(常用的)锁,操作系统会记录其持有线程(owner)是谁的信息。

那么究竟是怎么记的呢?我们能不能直接从锁的实例拿到这个 owner 信息呢?

三、实验和结果

基于《关于 dispatch_semaphore 优先级反转 》这篇贴子里描述的,记录了 owner 信息的最底层的锁有两种:pthread_mutex 和 os_unfair_lock 。NSLock 和 NSRecursiveLock 是基于 pthread_mutex 实现的,它们也比较常用,所以下面我们把这几种锁挨个试验一下:

1. pthread_mutex 互斥锁

示例代码:

//生成 mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//加锁
pthread_mutex_lock(&mutex);

网上搜索一下它的源码,发现在 pthread_mutex_t 结构体里有一个 data.owner 字段存的是相关的信息。

在 Xcode 中断点调试了一下,发现加完锁后, mutex 的 0x18 偏移处的确放了当前线程的 tid ,也即可以从锁实例直接定位其 owner 线程。

注:看内存可以在 lldb 中调用 x/64x &mutex ;查线程 tid 可以调用 thread info 命令。

所以取 pthread_mutex_t 类型的变量 mutex 的 owner 线程 tid 的取法为:

owner_tid = *(int *)((char *)&mutex + 0x18)

注:拿到 tid 以后可以调用 lldb 命令 thread list 来得到 tid 与常见的线程序号的对应关系
技术分享图片

2. NSLock 和 NSRecursiveLock 锁

示例代码:

NSLock *lock = [[NSLock alloc] init];
[lock lock];

NSRecursiveLock *recLock = [[NSRecursiveLock alloc] init];
[recLock lock];

这两种锁内部实现是基于 pthread_mutex_t 的(可以用 Hopper 直接反编译 Foundation ,也可以在 Xcode 里对 -[NSLock lock] 打断点看汇编)。

注:下文中附的是模拟器中的反汇编结果,其注释比较明确,真机的注释中的符号显示与实际不一致,容易误导读者。但实质上真机和模拟器上锁的内部实现是一致的,经测试取到的 tid 偏移也一样。

.

  • -[NSLock lock] 反汇编结果如下:
    技术分享图片

  • -[NSRecursiveLock lock] 反汇编结果如下:
    技术分享图片

其实现等价于:

// -[NSLock lock]
pthread_mutex_lock(object_getIndexedIvars(self)); //self 为 lock 实例

可以看出,可以直接对 lock 对象调用 object_getIndexedIvars 方法即可得到指向 pthread_mutex_t 实例的指针。

经测试,模拟器上和真机上调用 object_getIndexedIvars(lock) 的结果都是 (char *)lock + 0x10 ,所以:

NSLock 或 NSRecursiveLock 类型的变量 lock 的 owner 线程 id 的取法为:

owner_tid = *(int *)((char *)lock + 0x10 + 0x18);

3. os_unfair_lock 锁

示例代码:

os_unfair_lock unfairlock = OS_UNFAIR_LOCK_INIT;
os_unfair_lock_lock(&unfairlock);

使用同样的方法,经实验验证,os_unfair_lock类型的变量其 owner 线程取法为:

owner_tid = *(int *)((char *)&unfairlock       );

四、结论及展望

综上可知,通过简单的取偏移办法,就能在运行时定位到某个锁当前的 owner 线程。

后续可以写一个 lldb 脚本实现一个 owner_tid 命令,支持直接在 lldb 命令行里调用,调试起来就更方便了。

另外,卡顿和死锁检测模块也可以使用这个 owner 信息,帮助识别卡顿的根本原因。

注:本文主要是方案调研,笔者只在一两个版本的模拟器和真机上进行了试验来验证可行性。理论上这几个结构体应该不怎么会变,不过如果要真正投入线上使用,还是请在支持的系统版本上进行完整验证后再使用。

如何知道一个锁到底被哪个线程占用?

原文:https://blog.51cto.com/15064655/2573044

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!