Предлагает ли POSIX Thread API способ проверить, удерживает ли вызывающий поток блокировку? - PullRequest
0 голосов
/ 08 апреля 2019

Короткий вопрос: предлагает ли API-интерфейс потока POSIX способ определить, содержит ли вызывающий поток определенную блокировку?

Длинный вопрос:

Предположим, я хочу защитить структуру данных с помощью блокировки. Получение и снятие блокировки должно происходить в разных функциях. Вызовы между задействованными функциями довольно сложны (я добавляю многопоточность к 16-летней базе кода). Например:

  • do_dangerous_stuff() делает вещи, для которых текущий поток должен удерживать блокировку записи. Поэтому я получаю блокировку в начале функции и освобождаю ее в конце, так как вызывающая сторона не обязательно удерживает блокировку.
  • Другая функция, do_extra_dangerous_stuff(), вызывает do_dangerous_stuff(). Однако перед вызовом он уже выполняет действия, которые также требуют блокировки записи, и данные не согласованы до тех пор, пока не будет возвращен вызов do_dangerous_stuff() (поэтому снятие и немедленное повторное получение блокировки может привести к поломке).

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

С блокировкой чтения я мог бы просто получить ее несколько раз из одного и того же потока, при условии, что я освобождаю то же число экземпляров блокировки, которые я приобрел. Для блокировки записи это не вариант (попытка сделать это приведет к тупику). Простым решением было бы: проверить, удерживает ли текущий поток блокировку, и получить ее, если нет, и наоборот, проверить, сохраняет ли текущий поток блокировку, и снять ее, если она это делает. Тем не менее, для этого нужно проверить, удерживает ли текущий поток блокировку - есть ли способ сделать это?

Ответы [ 2 ]

1 голос
/ 09 апреля 2019

Глядя на справочную страницу для pthread_rwlock_wrlock(), я вижу, что там написано:

В случае успеха функция pthread_rwlock_wrlock() возвращает ноль;в противном случае возвращается номер ошибки, указывающий на ошибку.

[…]

Функция pthread_rwlock_wrlock() может завершиться ошибкой, если:

EDEADLK Текущий поток уже владеет блокировкой чтения-записи для записи или чтения.

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

Если я неправильно понял документациюПожалуйста, дайте мне знать. В противном случае решение будет просто позвонить pthread_rwlock_wrlock().Должно произойти одно из следующих действий:

  • Блокируется, поскольку ресурс удерживается другим потоком.Когда мы снова побежим, мы будем держать замок.Обычный бизнес.
  • Возвращает ноль (успех), потому что мы только что получили блокировку (которую мы раньше не держали).Обычный бизнес.
  • Возвращает EDEADLK, потому что мы уже держим замок.Не нужно повторно запрашивать, но мы могли бы рассмотреть это, когда снимаем блокировку - это зависит от кода, о котором идет речь, см. Ниже
  • Возвращает некоторую другую ошибку, указывающую, что что-то действительно пошло не так.То же, что и при любой другой операции блокировки.

Может иметь смысл отслеживать количество раз, когда мы получили блокировку и получили EDEADLK.Исходя из ответа Джила Гамильтона, глубина блокировки будет работать для нас:

  • Сбросьте глубину блокировки до 0, когда мы получим блокировку.
  • Увеличивайте глубину блокировки каждый раз на 1мы получаем EDEADLK.
  • Сопоставьте каждую попытку получения блокировки со следующим: Если глубина блокировки равна 0, снимите блокировку.Иначе уменьшите глубину блокировки.

Это должно быть поточно-ориентированным без дальнейшей синхронизации, так как глубина блокировки эффективно защищена замком, к которому она относится (мы касаемся только при удерживании замка).

Предупреждение: , если текущий поток уже удерживает блокировку чтения (а другие этого не делают), он также сообщит о том, что он «уже заблокирован».Необходимы дальнейшие тесты, чтобы определить, действительно ли текущая блокировка является блокировкой записи.Если несколько потоков, в том числе текущий, удерживают блокировку чтения, я не знаю, если попытка получить блокировку записи вернет EDEADLK или остановит поток.Эта часть требует дополнительной работы ...

0 голосов
/ 09 апреля 2019

AFAIK, нет простого способа выполнить то, что ты пытаешься сделать.

В Linux вы можете использовать атрибут «рекурсивный» мьютекс для достижения вашей цели (как показано здесь, например: https://stackoverflow.com/a/7963765/1076479),, но это не «posix» -портативный файл.

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

ПРЕДУПРЕЖДЕНИЕ: псевдокод на макушке моей головы

Рекурсивная блокировка:

// First try to acquire the lock without blocking...
if ((err = pthread_mutex_trylock(&mystruct.mutex)) == 0) {
    // Acquire succeeded. I now own the lock. 
    // (I either just acquired it or already owned it)
    assert(mystruct.owner == my_thread_index || mystruct.lock_depth == 0);
    mystruct.owner = my_thread_index;
    ++mystruct.lock_depth;
} else if (mystruct.owner == my_thread_index) {
    assert(err == EBUSY);
    // I already owned the lock. Now one level deeper
    ++mystruct.lock_depth;
} else {
    // I don't own the lock: block waiting for it.
    pthread_mutex_lock(&mystruct.mutex);
    assert(mystruct.lock_depth == 0);
}

На выходе это проще, потому что вы знаете, что владеете блокировкой, вынужно только определить, пора ли его отпустить (т.е. последняя разблокировка). Рекурсивная разблокировка:

if (--mystruct.lock_depth == 0) {
    assert(mystruct.owner == my_thread_index);
    // Last level of recursion unwound
    mystruct.owner = -1;    // Mark it un-owned
    pthread_mutex_unlock(&mystruct.mutex);
}

Я бы хотел добавить некоторые дополнительные проверки и утверждения, а также серьезное тестирование, прежде чем доверять этому тоже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...