pthread rwlock: rdlock внутри wrlock

Ситуация:

  • Программа использует pthread_rwlock_t, скажем, foolock
  • Поток, скажем T1, получает блокировку записи (полученную с помощью pthread_rwlock_wrlock()) на foolock
  • T1 пытается получить блокировку чтения (pthread_rwlock_rdlock()) на foolock
  • Никакие другие потоки не имеют ни блокировки чтения, ни блокировки записи на foolock
  • Есть соответствующие разблокировки.

Что ожидается?

Программа (в частности T1) получает ошибку:

pthread_rwlock_rdlock() returns EDEADLK ("Resource deadlock avoided").

Какова мотивация выбора такого поведения? В чем была бы проблема, если бы была предоставлена ​​​​блокировка чтения?

Что может быть хорошим выходом из этой ситуации? Возможно, T1 нужно поддерживать какое-то состояние, что он уже удерживает блокировку записи на foolock. Любое другое предложение?

Моя тестовая платформа — Linux 2.6.32-431.11.2.el6.x86_64, NPTL 2.12.

РЕДАКТИРОВАТЬ 1: Несколько пояснений:

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

Упрощенный контекст:

  • Я пытаюсь предоставить два общедоступных API: (1) find() и (2) update().
  • find() использует блокировку чтения
  • update() использует блокировку записи
  • update() реализация хочет вызвать find() ‹‹-- проблема

Мой текущий подход:

  • Пусть каждый общедоступный API имеет соответствующую частную версию без блокировки.
  • Публичный API выполняет 3 шага:

    • (a) acquire appropriate lock,
    • (b) вызвать приватную версию,
    • (c) снять блокировку

person Arun    schedule 02.07.2014    source источник


Ответы (2)


POSIX довольно ясно показывает, что в pthread_rwlock_rdlock():

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

и в pthread_rwlock_wrlock():

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

Я подозреваю, что это делается для простоты.

Возможность повышать блокировку чтения до блокировки записи выглядит полезной, но (к сожалению) это не так. Рассмотрим структуру данных «индекс», в которой «поиск» автоматически добавляет значения, которые не найдены: когда поиск не находит заданное значение, было бы неплохо активировать блокировку чтения и продолжить добавление значения в индекс. на том основании, что за это время ничего не могло измениться. К сожалению, это не сработает: рассмотрим два поиска, которые выполняются одновременно, и обоим нужна блокировка записи :-( Итак, заставляя программиста снять блокировку чтения и получить блокировку записи, по крайней мере, становится очень ясно, что это происходит.

Имейте в виду, если у «поиска по индексу» есть проблема, если он может удерживать «n» блокировок чтения в тот момент, когда он решает, что ему нужна блокировка записи, особенно если он не может узнать, что такое «n»: - (Было бы неплохо, по крайней мере, иметь возможность обнаружить 'n', запросив rwlock !

Попутно я обнаружил, что FreeBSD имеет:

rw_try_upgrade (структура rwlock *rw)

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

... но я отмечаю все оговорки о том, когда это действительно что-то сделает!


К вопросу о понижении блокировки записи до блокировки чтения...

... семантика довольно ясна, если нет ожидающих блокировок записи. Однако, если есть один или несколько ожидающих авторов, все не так просто.

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

Если бы операция «понижения» была определена как атомарная «запись-разблокировка/чтение-блокировка», то это могло бы позволить читателям опережать любых ожидающих записи или могло бы позволить читателям войти только в том случае, если нет ожидающих записи — второй кажется мне лучшим вариантом. Преимущество «понижения» состоит в том, что после понижения состояние больше не изменилось — я полагаю, это полезно.

Если бы pthread_rwlock_rdlock() при удержании блокировки записи было определено как «понижение», тогда нужно было бы четко указать, сколько уровней блокировки это приведет. Если результатом будут две блокировки, будет снята вторая (блокировка чтения) повторно получить первый (блокировка записи) или оставить его заблокированным для чтения? [Если бы было возможно «обновить» блокировку чтения до блокировки записи, как определить для работы произвольные последовательности блокировки чтения/блокировки записи/обновления/понижения/разблокировки?]

person Community    schedule 10.07.2014
comment
Спасибо за подробный ответ, но я НЕ ищу обновление или понижение версии, пожалуйста, смотрите соответствующее обновление. - person Arun; 10.07.2014
comment
В ПОРЯДКЕ. Ответ на вопрос, может ли запрос на блокировку чтения быть предоставлен, когда блокировка записи уже получена, согласно POSIX, что это может привести к взаимоблокировке. Итак, для чего-то портативного я предлагаю ответить «нет». Ваш эксперимент с Linux предполагает, что он отказывается от блокировки чтения, но на самом деле не блокируется - я предполагаю, что состояние блокировки не изменилось, но это всего лишь предположение. - person ; 11.07.2014

У вас уже есть блокировка чтения, поскольку блокировка записи позволяет чтение и запись.

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

Если вы действительно не можете переработать свой код, чтобы он знал, какие блокировки он удерживает, то я бы согласился с вашей идеей отслеживания этого состояния в какой-то структуре, специфичной для потока. Что-то похожее на тип lock-guard, популярный в C++, подойдет.

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

person Useless    schedule 10.07.2014
comment
Спасибо за подробный ответ, но я НЕ ищу рекурсивную блокировку, см. соответствующее обновление. - person Arun; 10.07.2014
comment
Ваша блокировка записи уже дает возможность безопасного чтения. Запрашивать еще одну блокировку чтения, когда вы уже удерживаете ее, аналогично рекурсивной блокировке, потому что вы повторно получаете то, что у вас уже есть. Сначала вы задали несколько вопросов, на которые я ответил: почему текущая реализация делает это? и могу ли я добиться желаемого поведения, сделав это? - person Useless; 11.07.2014