Блокировка эксклюзивно блокирует два ресурса.
Но не два одновременно, а напротив не допускает одновременного захвата обоих ресурсов.
Захвачен может быть только один ресурс, либо оба ресурса должны быть свободны.
Освобождение захваченного ресурса, происходит при захвате свободного ресурса.
Например, блокировка работает с 2-ми ресурсами A и B.
Предположим захвачен ресурс B
При захвате А, освобождается B
При освобождении А, B не захватывается, оба ресурса становятся свободными
И наоборот Предположим захвачен ресурс A
При захвате B, освобождается A
При освобождении B, A не захватывается, оба ресурса становятся свободными
Не обязательно чтобы работало наоборот, мне достаточно чтобы работало только в прямом направлении (то есть ресурсы асимметричны), но если будет работать и наоборот (то есть ресурсы симметричны), то это не помешает.
Если оба ресурса свободны, то захват одного из них не к чему не приводит.
Операция "захват и освобождение" должна быть атомарной (потокобезопасной)
Примерный код
Resource a = new Resource();
Resource b = new Resource();
using (DoubleLockSwitch dls = new DoubleLockSwitch(a, b)
{
dls.Lock(a);
//workif (condition)
{
dls.Lock(b);
// work
}
//work
}
Есть ли такая блокировка в готовом виде? Можно ли её реализовать с помощью других примитивов синхронизации?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>Блокировка эксклюзивно блокирует два ресурса. IB>Но не два одновременно, а напротив не допускает одновременного захвата обоих ресурсов. IB>Захвачен может быть только один ресурс, либо оба ресурса должны быть свободны. IB>Освобождение захваченного ресурса, происходит при захвате свободного ресурса.
IB>Например, блокировка работает с 2-ми ресурсами A и B.
IB>Предположим захвачен ресурс B IB>При захвате А, освобождается B IB>При освобождении А, B не захватывается, оба ресурса становятся свободными
IB>И наоборот IB>Предположим захвачен ресурс A IB>При захвате B, освобождается A IB>При освобождении B, A не захватывается, оба ресурса становятся свободными
Непонятно. Вы описали поведение только одного потока. Что должен наблюдать второй поток? Предположим, потоком I захвачена блокировка A.
Поток II пытается захватить блокировку B — что получает? Встаёт в ожидании; получает fail; успешно захватывает?
Если встаёт в ожидании, то вам не нужно иметь две блокировки, достаточно одной A+B. Если успешно захватывает, то в вашем коде — баг: как только поток I попробует захватить блокировку B, он встанет в ожидании.
Если поток II попробует вместо освобождения B захватить A, то вы поймаете deadlock.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
S>Непонятно. Вы описали поведение только одного потока. Что должен наблюдать второй поток? Предположим, потоком I захвачена блокировка A. S>Поток II пытается захватить блокировку B — что получает? Встаёт в ожидании; получает fail; успешно захватывает? S>Если встаёт в ожидании, то вам не нужно иметь две блокировки, достаточно одной A+B. Если успешно захватывает, то в вашем коде — баг: как только поток I попробует захватить блокировку B, он встанет в ожидании.
fail. В моем коде все потоки сначала захватывают блокировку А потом B.
S>Если поток II попробует вместо освобождения B захватить A, то вы поймаете deadlock.
не понял, почему поймаю deadlock? Если исходить из того, что S>Предположим, потоком I захвачена блокировка A.
То если поток II пробует захватить A, то он встаёт в ожидание.
Может я неправильно понял смысл "вместо освобождения B". Если их убрать смысл не поменяется. Что значит "вместо"?
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
S>>Непонятно. Вы описали поведение только одного потока. Что должен наблюдать второй поток? Предположим, потоком I захвачена блокировка A. S>>Поток II пытается захватить блокировку B — что получает? Встаёт в ожидании; получает fail; успешно захватывает? S>>Если встаёт в ожидании, то вам не нужно иметь две блокировки, достаточно одной A+B. Если успешно захватывает, то в вашем коде — баг: как только поток I попробует захватить блокировку B, он встанет в ожидании. IB>fail. В моем коде все потоки сначала захватывают блокировку А потом B.
Зачем тогда вам вообще такая "двойная блокировка"? Почему вам недостаточно иметь A+B?
S>>Если поток II попробует вместо освобождения B захватить A, то вы поймаете deadlock. IB>не понял, почему поймаю deadlock?
Потому, что поток II будет стоять в ожидании освобождения A. А оно случится только тогда, когда поток I успешно захватит B, что невозможно, потому что его уже занял поток II. S>>Предположим, потоком I захвачена блокировка A. IB>То если поток II пробует захватить A, то он встаёт в ожидание. IB>Может я неправильно понял смысл "вместо освобождения B". Если их убрать смысл не поменяется. Что значит "вместо"?
Это значит, что в вашей системе у владельца блокировки B возможно всего два действия: либо отпустить B, либо сконвертировать блокировку B в A.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Может, я немножко не по теме влезу, однако хочу упомянуть одну статью: Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps, by Joe Duffy. Была опубликована в журнале MSDN Magazine, потом мелкософт удалил онлайн-материалы и взамен предлагает скачивать журналы в формате chm. Взять можно здесь — April 2006. В ней автор приводит (в статье есть ссылка на скачку самораспаковывающегося архива с исходниками) класс LeveledLock, применение которого контролирует порядок взятия блокировок на разных ресурсах. Что позволит избежать дедлоков, о которых написал Sinclair.
Есть два ресурса (объекта) A и B.
Есть потоки которые всегда захватывают сначала A, а потом B. Это потоки типа N.
Есть потоки которые захватывают только B. Это потоки типа M.
Если пользоваться обычным обычным Monitor'ом, то получается следующее.
1) Поток I типа N захватывает ресурс A.
2) Теперь потоку I требуется переключиться на блокировку ресурса B. Для этого
2.1) Поток I освобождает ресурс А
2.2) Поток I захватывает ресурс B
Существует вероятность, что между пунктами 2.1 и 2.2 вклинится другой поток типа N и также захватит сначала A, потом B. Это недопустимо. Освобождение и захват должны быть атомарной операцией.
Кажется что можно поменять пункты 2.1 и 2.2 местами, то есть сначала захватывать B, а потом освобождать A. Но на сцену выходят потоки типа M. Поток типа M может захватить B до захвата потоком I блокировки B и тогда A не освободится до освобождения потоком типа M блокировки B, что недопустимо. Допустимо, если поток I переключится на блокировку B, и подождет пока её освободит поток типа M. При этом во время ожидания блокировка A будет свободна.
Я вижу пока только один способ, также пользуемся обычным обычным Monitor'ом но по-другому
1) Поток I типа N захватывает ресурс A.
2) Теперь потоку I требуется переключиться на блокировку ресурса B. Для этого
2.1) Поток I запускает дополнительный поток III. В этом потоке сначала захватывается B, потом выполняется код, который поток I хотел выполнить после переключения на блокировку ресурса B.
2.2) Поток I освобождает ресурс A
2.3) Поток I ждёт завершения потока III (.Join)
Хочется без создания дополнительных потоков. Может что-то с низкоуровневым примитивами синхронизации можно что-то замутить, может ReaderWriterLock как-то использовать
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
K>Может, я немножко не по теме влезу, однако хочу упомянуть одну статью: Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps, by Joe Duffy. Была опубликована в журнале MSDN Magazine, потом мелкософт удалил онлайн-материалы и взамен предлагает скачивать журналы в формате chm. Взять можно здесь — April 2006. В ней автор приводит (в статье есть ссылка на скачку самораспаковывающегося архива с исходниками) класс LeveledLock, применение которого контролирует порядок взятия блокировок на разных ресурсах. Что позволит избежать дедлоков, о которых написал Sinclair.
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml
Здравствуйте, igor-booch, Вы писали:
IB>опишу задачу по другому, конкретно что нужно:
IB>Есть два ресурса (объекта) A и B. IB>Есть потоки которые всегда захватывают сначала A, а потом B. Это потоки типа N. IB>Есть потоки которые захватывают только B. Это потоки типа M. IB>Если пользоваться обычным обычным Monitor'ом, то получается следующее.
IB>1) Поток I типа N захватывает ресурс A. IB>2) Теперь потоку I требуется переключиться на блокировку ресурса B. Для этого IB> 2.1) Поток I освобождает ресурс А IB> 2.2) Поток I захватывает ресурс B
IB>Существует вероятность, что между пунктами 2.1 и 2.2 вклинится другой поток типа N и также захватит сначала A, потом B. Это недопустимо. Освобождение и захват должны быть атомарной операцией. IB>Кажется что можно поменять пункты 2.1 и 2.2 местами, то есть сначала захватывать B, а потом освобождать A. Но на сцену выходят потоки типа M. Поток типа M может захватить B до захвата потоком I блокировки B и тогда A не освободится до освобождения потоком типа M блокировки B, что недопустимо. Допустимо, если поток I переключится на блокировку B, и подождет пока её освободит поток типа M. При этом во время ожидания блокировка A будет свободна.
Выделенные требования несовместимы. Следите за руками:
1. Поток N1 захватил А
2. Поток М1 захватил B
3. Поток N1 пытается сконвертировать А в В. По вашему требованию, он уже отпустил А и теперь стоит в ожидании B
4. Поток N2 захватывает А
5. Поток N2 пытается сконвертировать А в В.
6. Поток M1 отпускает B.
В этот момент у потоков N1 и N2 равные шансы захватить B. Это означает, что с вероятностью 50% произойдёт сценарий вклинивания N2 внутрь транзакции, выполняемой N1, что по вашему второму требованию запрещено.
IB>Я вижу пока только один способ, также пользуемся обычным обычным Monitor'ом но по-другому
IB>1) Поток I типа N захватывает ресурс A. IB>2) Теперь потоку I требуется переключиться на блокировку ресурса B. Для этого IB> 2.1) Поток I запускает дополнительный поток III. В этом потоке сначала захватывается B, потом выполняется код, который поток I хотел выполнить после переключения на блокировку ресурса B. IB> 2.2) Поток I освобождает ресурс A IB> 2.3) Поток I ждёт завершения потока III (.Join)
Вы же понимаете, что порядок выполнения 2.1 и 2.2 ничем не гарантирован? Т.е. поток I может освободить ресурс А задолго до того, как III захватит B. Поэтому функционально это решение эквивалентно неатомарному освобождению А, а затем захвату B. IB>Хочется без создания дополнительных потоков. Может что-то с низкоуровневым примитивами синхронизации можно что-то замутить, может ReaderWriterLock как-то использовать
Я всё ещё не понимаю природы ваших ограничений. На первый взгляд, задача решается в том случае, если доступ к B будет рулиться не монитором, а очередью — чтобы гарантировать, что потоки получают доступ к B в том же порядке, как они получали доступ к А.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, igor-booch, Вы писали:
IB>Есть два ресурса (объекта) A и B. IB>Есть потоки которые всегда захватывают сначала A, а потом B. Это потоки типа N. IB>Есть потоки которые захватывают только B. Это потоки типа M.
А и B не связаны. Всем потокам (N и M) нужен B. Ресурс А необходимо только N потокам, они могут сначала захватить B, а уже затем отпускать А.
S>В этот момент у потоков N1 и N2 равные шансы захватить B. Это означает, что с вероятностью 50% произойдёт сценарий вклинивания N2 внутрь транзакции, выполняемой N1, что по вашему второму требованию запрещено.
Вот и не должны быть равные шансы. Приоритет должен быть отдан N1, а N2 должен встать в очередь, так как N1 захватил A раньше, чем N2. Кто первым захватывает A, тот первым захватывает B. Вопрос топика как это реализовать?
IB>>Я вижу пока только один способ, также пользуемся обычным обычным Monitor'ом но по-другому
IB>>1) Поток I типа N захватывает ресурс A. IB>>2) Теперь потоку I требуется переключиться на блокировку ресурса B. Для этого IB>> 2.1) Поток I запускает дополнительный поток III. В этом потоке сначала захватывается B, потом выполняется код, который поток I хотел выполнить после переключения на блокировку ресурса B. IB>> 2.2) Поток I освобождает ресурс A IB>> 2.3) Поток I ждёт завершения потока III (.Join) S>Вы же понимаете, что порядок выполнения 2.1 и 2.2 ничем не гарантирован? Т.е. поток I может освободить ресурс А задолго до того, как III захватит B. Поэтому функционально это решение эквивалентно неатомарному освобождению А, а затем захвату B.
Да это проблема, согласен
перед пунктом 2.2 думал покрутиться в цикле
while (thread.ThreadState == ThreadState.Unstarted) {}
Но это тоже не даёт гарантии.
Thread.MemoryBarrier не знаю, может ли помочь в этом случае
Вот и вопрос. Как гарантировать, что B будет захвачена в дополнительном потоке III (п. 2.1), раньше чем освободится A (п. 2.2)?
IB>>Хочется без создания дополнительных потоков. Может что-то с низкоуровневым примитивами синхронизации можно что-то замутить, может ReaderWriterLock как-то использовать S>Я всё ещё не понимаю природы ваших ограничений. На первый взгляд, задача решается в том случае, если доступ к B будет рулиться не монитором, а очередью — чтобы гарантировать, что потоки получают доступ к B в том же порядке, как они получали доступ к А.
Спасибо, возможно это и есть решение: заюзать очередь и синхронизировать Monitor'ом добавление в неё Action'ов, которые должны выполняться либо в блокировке A, либо в блокировке B
Отвечайте на это сообщение, только если у Вас хорошее настроение и в Вашем ответе планируются только конструктивные вопросы и замечания http://rsdn.ru/Info/rules.xml