Linux: Как я могу найти stream, который содержит определенный замок?

У меня есть многопоточная программа, которая работает в Linux, иногда, если я запускаю gstack против нее, stream уже долго ждал блокировки (скажем, 2-3 минуты),

Тема 2 (Thread 0x5e502b90 (LWP 19853)):

0 0x40000410 в __kernel_vsyscall ()

1 0x400157b9 в __lll_lock_wait () из /lib/i686/nosegneg/libpthread.so.0

2 0x40010e1d в _L_lock_981 () из /lib/i686/nosegneg/libpthread.so.0

3 0x40010d3b в pthread_mutex_lock () из /lib/i686/nosegneg/libpthread.so.0

Я проверил остальные streamи, но никто из них не занимался этим замком, однако через некоторое время этот stream (LWP 19853) мог успешно захватить этот замок.

Там должен существовать один stream, который уже приобрел этот замок, но я не смог его найти, есть ли что-то, что я пропустил?

EDIT: определение pthread_mutex_t:

объединение typedef

{

struct __pthread_mutex_s {

int __lock;

unsigned int __count;

int __owner;

/ * KIND должен оставаться в этой позиции в структуре, чтобы поддерживать двоичную совместимость. * /

int __kind;

unsigned int __nusers;

расширение union {int __spins; __pthread_slist_t __list; };

} __данные;

char _ size [ _SIZEOF_PTHREAD_MUTEX_T];

long int __align;

} pthread_mutex_t;

Существует член «__owner», это идентификатор streamа, который держит мьютекс сейчас.

2-3 минуты звучит много, но если ваша система находится под большой нагрузкой, нет никакой гарантии, что ваш stream просыпается сразу после того, как другой разблокирует мьютекс. Таким образом, может быть просто нет нити (больше), которая удерживает блокировку в тот момент, когда вы смотрите на нее.

Мьютекс Linux работает в два этапа. Грубо говоря:

  • На первом этапе выполняется атомарная операция CAS по значению int чтобы проверить, можно ли сразу заблокировать мьютекс.
  • Если это невозможно, системный вызов futex_wait с адресом того же int передается ядру.

Затем операция разблокировки заключается в изменении значения обратно на начальное значение (обычно 0 ) и выполнение системного вызова futex_wake . Затем kernel futex_wait что кто-то зарегистрировал вызов futex_wait по тому же адресу и оживил эти streamи в очереди планирования. Какой stream действительно проснулся и зависит от разных вещей, в частности от политики планирования, которая включена. Нет гарантии, что streamи получат блокировки в том порядке, в котором они были размещены.

Мьютексы по умолчанию не отслеживают stream, который их блокировал. (Или, по крайней мере, я этого не знаю)

Существует два способа отладки этой проблемы. Одним из способов является регистрация каждого замка и разблокировка. При каждом создании streamа вы регистрируете значение идентификатора streamа, который был создан. Сразу после блокировки блокировки вы регистрируете идентификатор streamа и имя заблокированной блокировки (для этого вы можете использовать файл / строку или назначить имя каждой блокировке). И вы снова регистрируетесь перед разблокировкой любой блокировки.

Это прекрасный способ сделать это, если ваша программа не имеет десятков нитей и более. После этого журналы начинают становиться неуправляемыми.

Другим способом является объединение вашей блокировки в class, который хранит идентификатор streamа в объекте блокировки сразу после каждой блокировки. Вы даже можете создать глобальный реестр блокировки, который отслеживает это, что вы можете распечатать, когда вам нужно.

Что-то вроде:

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

Ключ здесь – не применять ни один из этих методов для вашей версии. Как глобальный журнал блокировки, так и глобальный реестр состояний блокировки создают один глобальный ресурс, который сам станет ресурсом в конфликте блокировок.

API POSIX не содержит функции, которая это делает.

Также возможно, что на некоторых платформах реализация не позволяет этого.
Например, блокировка может использовать атомную переменную, установленную в 1 при блокировке. Нить, получающая ее, не должна записывать свой идентификатор в любом месте, поэтому никакая функция не может ее найти.

Для таких проблем отладки вы можете добавить в свою программу специальные вызовы регистрации, в которых указывается, когда протекторы приобрели блокировку и когда она вернула ее.

Такие записи журнала помогут вам найти, какая нить заняла блокировку.

В любом случае, это может привести к серьезному изменению времени выполнения программы, и проблема, которая будет отлажена, больше не будет казаться самой classической, как часто встречающаяся в многопоточных приложениях.