我有一个在Linux上运行的多线程程序,有时如果我对它运行gstack,有一个线程正在等待锁定很长时间(比如2-3分钟),
线程2(线程0x5e502b90(LWP 19853)):
__kernel_vsyscall()中的0 0x40000410 来自/lib/i686/nosegneg/libpthread.so.0的__lll_lock_wait()中的
1 0x400157b9
来自/lib/i686/nosegneg/libpthread.so.0的_L_lock_981()中的2 0x40010e1d
来自/lib/i686/nosegneg/libpthread.so.0的pthread_mutex_lock()中的3 0x40010d3b
...
我检查了剩下的线程,没有人拿这个锁,但是,一段时间后这个线程(LWP 19853)可以成功获得这个锁。
应该存在一个已经获得此锁定的线程,但我找不到它,是否有任何遗漏?
编辑: pthread_mutex_t的定义:
typedef union
{
struct __pthread_mutex_s {
int __lock;
unsigned int __count;
int __owner;
/ * KIND必须保持在结构中的这个位置才能维持 二进制兼容性* /
int __kind;
unsigned int __nusers;
扩展联盟 { int __spins; __pthread_slist_t __list; };
} __ data;
char _ size [ _SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
有一个成员“__owner”,它是现在持有互斥锁的线程的id。
答案 0 :(得分:2)
2-3分钟听起来很多,但如果您的系统负载很重,则无法保证您的线程在另一个解锁互斥锁后立即唤醒。因此,在您正在查看它的那一刻,可能只有没有线程(不再)持有锁。
Linux互斥锁分两个阶段工作。大致是:
int
值进行原子CAS操作以查看是否存在
可以立即锁定互斥锁。futex_wait
地址的int
系统调用传递给内核。解锁操作包括将值更改回初始值(通常为0
)并进行futex_wake
系统调用。然后内核查看是否有人在同一地址上注册了futex_wait
调用,并在调度队列中恢复这些线程。真正被唤醒的线程以及何时取决于不同的事物,特别是启用的调度策略。无法保证线程按照放置锁定的顺序获取锁定。
答案 1 :(得分:2)
默认情况下,互斥锁不会跟踪锁定它们的线程。 (或者至少我不知道这样的事情)
有两种方法可以调试此类问题。一种方法是记录每个锁并解锁。在每个线程创建时,您都会记录创建的线程ID的值。在锁定任何锁定之后,您记录线程ID和锁定的锁定的名称(您可以使用文件/行,或为每个锁定一个名称)。并且在解锁任何锁之前再次登录。
如果您的程序没有数十个或更多线程,这是一个很好的方法。之后,日志开始变得无法管理。
另一种方法是将锁包装在一个类中,该类在每次锁定后立即将线程id存储在锁定对象中。您甚至可以创建一个跟踪它的全局锁定注册表,您可以在需要时打印出来。
类似的东西:
class MyMutex
{
public:
void lock() { mMutex.lock(); mLockingThread = getThreadId(); }
void unlock() { mLockingThread = 0; mMutex.unlock(); }
SystemMutex mMutex;
ThreadId mLockingThread;
};
这里的关键是 - 不要为您的发布版本实现这些方法中的任何一种。全局锁定日志或锁定状态的全局注册表都会创建一个单独的全局资源,该资源本身将成为锁争用下的资源。
答案 2 :(得分:0)
POSIX API不包含执行此操作的功能。
在某些平台上,实施也不允许这样做 例如,锁可以使用原子变量,锁定时设置为1。获取它的线程不必在任何地方写入它的ID,所以没有函数可以找到它。
答案 3 :(得分:0)
对于这样的调试问题,您可能会向程序添加两个特殊的日志记录调用,说明哪个步骤获取了锁定以及何时返回它。
这样的日志条目将帮助您找到最后获取锁的线程。
无论如何这样做可能会大规模地改变程序的运行时行为,并且要调试的问题将不再像在多线程应用程序中经常看到的经典heisenbug那样出现。