确定正在持有互斥锁的线程是否可能?

90

首先,我使用pthread库编写多线程C程序。线程总是在等待其互斥锁时挂起。当我使用strace工具查找处于FUTEX_WAIT状态的线程时,我想知道此时哪个线程持有该互斥锁。但我不知道怎么做。是否有任何工具可以实现这一点?

有人告诉我Java虚拟机支持此功能,所以我想知道Linux是否支持此功能。

4个回答

150

您可以利用互斥锁的内部知识来实现此操作。通常这不是一个很好的想法,但在调试时没问题。

在Linux下,使用pthread的NPTL实现(也就是任何现代的glibc),您可以检查 pthread_mutex_t 结构的 __data.__owner 成员以找到当前持有锁的线程。以下是如何在使用 gdb 附加到进程后执行此操作:

(gdb) thread 2
[Switching to thread 2 (Thread 0xb6d94b90 (LWP 22026))]#0  0xb771f424 in __kernel_vsyscall ()
(gdb) bt
#0  0xb771f424 in __kernel_vsyscall ()
#1  0xb76fec99 in __lll_lock_wait () from /lib/i686/cmov/libpthread.so.0
#2  0xb76fa0c4 in _L_lock_89 () from /lib/i686/cmov/libpthread.so.0
#3  0xb76f99f2 in pthread_mutex_lock () from /lib/i686/cmov/libpthread.so.0
#4  0x080484a6 in thread (x=0x0) at mutex_owner.c:8
#5  0xb76f84c0 in start_thread () from /lib/i686/cmov/libpthread.so.0
#6  0xb767784e in clone () from /lib/i686/cmov/libc.so.6
(gdb) up 4
#4  0x080484a6 in thread (x=0x0) at mutex_owner.c:8
8               pthread_mutex_lock(&mutex);
(gdb) print mutex.__data.__owner
$1 = 22025
(gdb)

我切换到了挂起的线程,执行回溯以找出它所卡在的pthread_mutex_lock(),更改栈帧以查找该互斥锁的名称,然后打印该互斥锁的所有者。这告诉我LWP ID为22025的线程是罪魁祸首。

然后,您可以使用 thread find 22025 查找该线程的gdb线程号并切换到它。


1
有没有一种方法可以将__data__.__owner__与pthread线程ID相关联?在尝试中,我只是编写了log << mutex.data.owner << endl,这似乎运行良好。但是,data.owner是像9841这样的值,而tid则像140505876686608这样。这两个值之间的关系是什么? - Duck
6
@Duck:.__data.__owner 中的值是 TID。当每个线程启动时,你可以让它们记录他们的 TID(使用 tid = syscall(SYS_gettid);),以及他们的 pthread_t(从 pthread_self() 获得)。 - caf
1
您还可以在proc文件的stat中检查线程的堆栈指针,它将非常接近(在几KB范围内)于pthread_t值。 :-) - R.. GitHub STOP HELPING ICE
3
顺便提一句:可以使用“info threads”命令将TID(“.__data.__owner”)映射到pthread ID(在gdb中操作的ID)。 - Adam Romanek
11
@caf,你可以在回答中补充一点内容,即现在在gdb中有thread find命令。因此,在找到mutex.__data.__owner等于22025之后,你可以运行:thread find 22025并在gdb中获取线程编号:(例如:Thread 29 has target id 'Thread 0x7fffdf5fe700 (LWP 22025)')。然后,你可以使用命令thread 29或者简写为t 29`来切换到持有该锁的线程。 - user184968
根据这个答案__owner字段有时未被填充,因此为0(无效)。 - Tor Klingberg

5
我不知道有任何这样的设施,所以我认为你不会轻易获得帮助——而且它可能没有你想象的那么有用来帮助调试程序。尽管看起来技术含量很低,但记录日志是调试这些问题的好帮手。开始收集你自己的小型日志函数。它们不必太复杂,只要在调试时能完成工作即可。
抱歉,以下是C++代码示例:
void logit(const bool aquired, const char* lockname, const int linenum)
{
    pthread_mutex_lock(&log_mutex);

    if (! aquired)
        logfile << pthread_self() << " tries lock " << lockname << " at " << linenum << endl;
    else
        logfile << pthread_self() << " has lock "   << lockname << " at " << linenum << endl;

    pthread_mutex_unlock(&log_mutex);
}


void someTask()
{
    logit(false, "some_mutex", __LINE__);

    pthread_mutex_lock(&some_mutex);

    logit(true, "some_mutex", __LINE__);

    // do stuff ...

    pthread_mutex_unlock(&some_mutex);
}

日志记录并不是完美的解决方案,但没有什么是完美的。通常可以帮助你获得所需的信息。


记录确实是调试的有用工具。感谢您的建议。 - terry
1
谁不喜欢日志记录呢?使用LD_PRELOAD(和一些耐心),可以在不进行代码更改的情况下完成。将pthread_mutex_*函数包装在某些东西中,以记录函数调用、互斥锁地址和线程标识符(pthread_t在Linux上恰好是一个整数类型,这不是一个可移植的假设,但相当方便)。 - pilcrow
9
记录日志可能存在的问题是它可能会干扰计时并使问题消失。 - Spudd86
同时,您并不能总是/可预测地插入库函数。这并不是一个保证。 - Matt Joiner
日志记录非常有用。但是,在某些情况下,日志记录并不安全。具体来说,malloc在某些地方是不安全的 - 例如,在信号处理程序、atfork处理程序、多线程程序中的fork和exec之间等地方。请参阅async-signal-safety和其他手册页面。 - mgarey

3

这就是答案。不知道为什么它不是最受欢迎的答案。 - Hervé

2

通常情况下,libc/platforms调用由操作系统抽象层进行抽象。可以使用owner变量和pthread_mutex_timedlock跟踪互斥死锁。每当线程锁定时,它应该使用自己的tid(gettid(),也可以有另一个变量用于pthread id存储)更新变量。因此,当其他线程在pthread_mutex_timedlock上阻塞并超时时,它可以打印owner tid和pthread_id的值。这样你就可以轻松找到所有者线程。请查看下面的代码片段,请注意没有处理所有错误条件。

pid_t ownerTid;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

class TimedMutex {
    public:
        TimedMutex()
        {
           struct timespec abs_time;

           while(1)
           {
               clock_gettime(CLOCK_MONOTONIC, &abs_time);
               abs_time.tv_sec += 10;
               if(pthread_mutex_timedlock(&mutex,&abs_time) == ETIMEDOUT)
               {
                   log("Lock held by thread=%d for more than 10 secs",ownerTid);
                   continue;
               }
               ownerTid = gettid();
           }
        }

        ~TimedMutex()
        {

             pthread_mutex_unlock(&mutex);  
        }
};

还有其他方法可以找出死锁,也许这个链接可以帮助:http://yusufonlinux.blogspot.in/2010/11/debugging-core-using-gdb.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接