【问题标题】:Is it possible to determine the thread holding a mutex?是否可以确定持有互斥锁的线程?
【发布时间】:2023-03-19 20:38:01
【问题描述】:

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

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

【问题讨论】:

    标签: c linux multithreading pthreads mutex


    【解决方案1】:

    您可以使用互斥体内部知识来执行此操作。通常这不是一个好主意,但它可以用于调试。

    在具有 pthreads 的 NPTL 实现的 Linux 下(这是任何现代 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 线程号并切换到它。

    【讨论】:

    • 有没有办法将 data.__owner__ 与 pthread 线程 id 相关联?在玩这个时,我简单地编写了 log
    • @Duck:.__data.__owner 中的值是一个 TID。当每个线程启动时,您可以让他们记录他们的 TID(使用tid = syscall(SYS_gettid);)以及他们的pthread_t(来自pthread_self())。
    • 您还可以检查procstat 文件中的线程堆栈指针,它与pthread_t 值非常接近(在几kb 内)。 :-)
    • 顺便说一句:可以使用 info threads 将 TID (.__data.__owner) 映射到 pthread ID(在 gdb 中操作的 ID)。
    • @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
    【解决方案2】:

    我不知道有任何这样的工具,所以我认为您不会那么容易摆脱 - 而且它可能不会像您想象的那样提供帮助调试程序的信息。尽管看起来技术含量很低,但日志记录是您调试这些东西的朋友。开始收集你自己的小日志功能。他们不必花哨,他们只需要在调试的同时完成工作。

    对 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);
    }
    

    记录不是一个完美的解决方案,但没有什么是完美的。它通常会为您提供您需要了解的信息。

    【讨论】:

    • 日志记录确实是非常有用的调试工具。感谢您的建议。
    • +1 谁不喜欢伐木?可以使用 LD_PRELOAD(和一些耐心)在不更改代码的情况下完成。用记录函数调用、互斥体地址和线程标识符的内容包装 pthread_mutex_* 函数(pthread_t 在 Linux 上恰好是一个整数类型,不是可移植的假设,但非常方便)。
    • 日志记录的可能问题是它可能会破坏时间并使问题消失。
    • 您也不能总是/可预测地插入库函数。这不是保证。
    • 日志记录非常有用。但是,有些地方记录不安全。具体来说,malloc 在某些地方是不安全的——例如,在信号处理程序、atfork 处理程序、多线程程序中的 fork 和 exec 之间等。请参阅async-signal-safety 和其他手册页。
    【解决方案3】:

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

    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有帮助。

    【讨论】:

      【解决方案4】:

      请阅读下面的链接,这有一个寻找锁所有者的通用解决方案。即使锁定在库中并且您没有源代码,它也可以工作。

      https://en.wikibooks.org/wiki/Linux_Applications_Debugging_Techniques/Deadlocks

      【讨论】:

      • 这是的答案。不知道为什么它不是票数最高的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-05-03
      • 2021-11-07
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2012-12-25
      • 2017-03-02
      相关资源
      最近更新 更多