【问题标题】:Linux: How can I find the thread which holds a particular lock?Linux:如何找到持有特定锁的线程?
【发布时间】:2012-07-08 15:14:01
【问题描述】:

我有一个在 Linux 上运行的多线程程序,有时如果我对它运行 gstack,有一个线程等待锁定很长时间(比如 2-3 分钟),

线程 2(线程 0x5e502b90 (LWP 19853)):

0 0x40000410 in __kernel_vsyscall()

1 0x400157b9 in __lll_lock_wait () from /lib/i686/nosegneg/libpthread.so.0

2 0x40010e1d in _L_lock_981 () from /lib/i686/nosegneg/libpthread.so.0

3 0x40010d3b in pthread_mutex_lock () from /lib/i686/nosegneg/libpthread.so.0

...

我检查了其余线程,没有一个线程获得了这个锁,但是,过了一会儿,这个线程 (LWP 19853) 可以成功地获得这个锁。

应该有一个线程已经获得了这个锁,但是我没找到,有什么遗漏的吗?

编辑: pthread_mutex_t的定义:

typedef 联合

{

结构 __pthread_mutex_s {

int __lock;

unsigned int __count;

int __owner;

/* KIND 必须停留在结构中的这个位置以保持 二进制兼容性。 */

int __kind;

无符号整数 __nuers;

扩展联合 { int __spins; __pthread_slist_t __list; };

} __data;

字符_大小[_SIZEOF_PTHREAD_MUTEX_T];

long int __align;

} pthread_mutex_t;

有一个成员“__owner”,它是现在持有互斥锁的线程的id。

【问题讨论】:

标签: c linux pthreads


【解决方案1】:

默认情况下,互斥锁不跟踪锁定它们的线程。 (或者至少我不知道这样的事情)

有两种方法可以调试此类问题。一种方法是记录每个锁定和解锁。在每次创建线程时,您都会记录创建的线程 id 的值。锁定任何锁后,立即记录线程 ID 和被锁定的锁的名称(您可以为此使用文件/行,或为每个锁分配一个名称)。然后在解锁任何锁之前再次登录。

如果您的程序没有数十个或更多线程,这是一个很好的方法。之后,日志开始变得难以管理。

另一种方法是将锁包装在一个类中,该类在每个锁之后将线程 ID 存储在锁对象中。您甚至可以创建一个全局锁定注册表来跟踪它,您可以在需要时打印出来。

类似:

class MyMutex
{
public:
    void lock() { mMutex.lock(); mLockingThread = getThreadId(); }
    void unlock() { mLockingThread = 0; mMutex.unlock(); }
    SystemMutex mMutex;
    ThreadId    mLockingThread;
};

这里的关键是 - 不要为您的发布版本实现这些方法中的任何一种。全局锁定日志或锁定状态的全局注册表都会创建一个全局资源,该资源本身将成为锁定争用的资源。

【讨论】:

  • 查看pthread_mutex_t的定义可以发现,它有一个成员变量owner,可以表示持有线程信息。
  • 如果我们能够获得该运行应用程序的核心转储,那么我们就能够找到正在获取 mutext 的线程。通常,使用 gstack 输出,我可以通过读取调用堆栈找到持有互斥锁的线程,但在这种情况下,我无法找到它,只有 gstack 输出可用。
  • 不知道有没有办法使用pthread自带的线程跟踪。在那种情况下,我不知道为什么在某些情况下跟踪似乎失败了。我知道使用一些线程和互斥类自己跟踪线程很容易。
【解决方案2】:

对于此类调试问题,您可能会在程序中添加两个特殊的日志记录调用,说明哪个线程何时获得锁以及何时返回锁。

这样的日志条目将帮助您找到最后获得锁的线程。

无论如何,这样做可能会极大地改变程序的运行时行为,并且要调试的问题将不再像多线程应用程序中常见的经典 heisenbug 那样出现。

【讨论】:

    【解决方案3】:

    2-3 分钟听起来很长,但如果您的系统负载过重,则无法保证您的线程在另一个线程解锁互斥锁后立即唤醒。因此,在您查看它的那一刻,可能没有线程(不再)持有锁。

    Linux 互斥锁分两个阶段工作。大致:

    • 在第一阶段,对int 值进行原子 CAS 操作,以查看 互斥锁可以立即锁定。
    • 如果无法做到这一点,则将具有相同地址的futex_wait 系统调用传递给内核int

    然后解锁操作包括将值改回初始值(通常为0)并执行futex_wake 系统调用。然后内核查看是否有人在同一地址上注册了futex_wait 调用,并在调度队列中恢复这些线程。真正唤醒哪个线程以及何时唤醒取决于不同的事情,特别是启用的调度策略。无法保证线程按照它们放置它们的顺序获得锁。

    【讨论】:

    • 这必须是一个真正负载的系统,以使线程在几分钟后无法唤醒。无论如何我同意你的回答!
    • 这里的事情是我查看了每个线程的调用堆栈,没有一个线程已经获得了这个锁,所以我很困惑的是为什么这个线程(线程2)没有立即锁定此互斥体,因为没有人使用它。
    • @ageek2remember,您确定没有线程使用trylock 并且您正确初始化了互斥锁吗?如果你这样做了,一种知道谁在获取锁的方法是在初始化后立即放置一个锁,让执行正常进行,直到其中一个线程阻塞。那么那个人就是罪魁祸首。
    • 是的,至少在阅读 gstack 输出后我确定。这是在生产中,我无法在我的实验室中重现。所以现在将代码更改为调试不是一种选择..
    【解决方案4】:

    POSIX API 不包含执行此操作的函数。

    也有可能在某些平台上,实现不允许这样做。
    例如,锁可以使用原子变量,在锁定时设置为 1。获取它的线程不必在任何地方写它的ID,所以没有函数可以找到它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多