в чем состоит зло рекурсивных блокировок?
От: sergunok  
Дата: 22.11.10 15:23
Оценка:
если они используются в коде аккуратно (с использованием exception-safe и "return-safe" блоков)?

либо старым добрым способом:

lock(_lock)
{
....
}


либо для читателей и писателей так:


_readerWriterLock.EnterReaderLock();
try
{
  ...
}
finally
{
  _readerWriterLock.ExitReaderLock();
}


или так


_readerWriterLock.EnterWriterLock();
try
{
  ...
}
finally
{
  _readerWriterLock.ExitWriterLock();
}


или так


_readerWriterLock.EnterUpgradableReaderLock();
try
{
  
  ...
  if(...)
  {
    _readerWriterLock.EnterWriterLock();  
    try
    {
      ..
    }
    finally
    {
      _readerWriterLock.ExitWriterLock();  
    }
  }
}
finally
{
  _readerWriterLock.ExitUpgradableReaderLock();
}


где зло?
Re: в чем состоит зло рекурсивных блокировок?
От: cvetkov  
Дата: 22.11.10 16:00
Оценка: 2 (1) +1
Зло вот в чем.
lock(_lock)
{
    setA();
    foo();
    setB();
}

void foo(){
lock(_lock)
{
    //а тут мы считаем что мы находимся в консистентном состоянии
    //а это нифига не так
}
}
... << RSDN@Home 1.2.0 alpha 4 rev. 1227>>
Re[2]: в чем состоит зло рекурсивных блокировок?
От: sergunok  
Дата: 22.11.10 16:13
Оценка:
Здравствуйте, cvetkov, Вы писали:

C>Зло вот в чем.

C>
C>lock(_lock)
C>{
C>    setA();
C>    foo();
C>    setB();
C>}

C>void foo(){
C>lock(_lock)
C>{
C>    //а тут мы считаем что мы находимся в консистентном состоянии
C>    //а это нифига не так
C>}
C>}

C>


Про консистентное состояние мне неочевидно.


Чем к примеру плохо вот такое (конкретизирую ваш пример):

class Collection<T>
{
...
private readonly object sync = new object();

public object Sync { get { return this.sync; } }

public Add(T item)
{
lock(this.sync)
{
...
}
}

public bool Contains(T item)
{
lock(this.sync)
{
...
}
}

}


где-то потом:


....

lock(collection.Sync)
{
if(!collection.Contains(item))
collection.Add(item);
}
Re[2]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 16:15
Оценка:
Здравствуйте, cvetkov, Вы писали:

C> //а тут мы считаем что мы находимся в консистентном состоянии

C> //а это нифига не так

Почему? В смысле, что Вы подразумеваете под консистентным состоянием в данном случае?
"Нормальные герои всегда идут в обход!"
Re: в чем состоит зло рекурсивных блокировок?
От: dilmah США  
Дата: 22.11.10 16:16
Оценка: +2
а один я недоумеваю что делает этот общий вопрос в форуме .NET?
Re[3]: в чем состоит зло рекурсивных блокировок?
От: cvetkov  
Дата: 22.11.10 16:39
Оценка: -1
Здравствуйте, sergunok, Вы писали:

S>Про консистентное состояние мне неочевидно.


int a = 0;
int b = 0;
void foo(){
    lock(this){
        assert a+b=0;//ok
        int a = newA();
        int tmpB = newB();
        assert a+tmpB=0;//ok
        bar();
        b = tmpB;//fail
        //а тут мы экспортируем нарушение инварианта наружу.
    }
}
void foo(){
    lock(this){
        //тут мы надеемся что все в порядке a+b=0, но это не так
        assert a+b=0;//fail
        //тут код зависящий от инвареанта
        a++;
        b--;
    }
}



S>Чем к примеру плохо вот такое (конкретизирую ваш пример):


ничем. никто и не говорил что проблемы будут всегда.
просто проблемы возможны.
и для того чтобы их избежать надо увеличить расход внимательности на строчку кода, а этот ресурс ограничен.
... << RSDN@Home 1.2.0 alpha 4 rev. 1227>>
Re[4]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 17:00
Оценка:
Здравствуйте, cvetkov, Вы писали:

C>Здравствуйте, sergunok, Вы писали:


S>>Про консистентное состояние мне неочевидно.


C>
C>int a = 0;
C>int b = 0;
C>void foo(){
C>    lock(this){
C>        assert a+b=0;//ok
C>        int a = newA();
C>        int tmpB = newB();
C>        assert a+tmpB=0;//ok
C>        bar();
C>        b = tmpB;//fail
C>        //а тут мы экспортируем нарушение инварианта наружу.
C>    }
C>}
C>void foo(){
C>    lock(this){
C>        //тут мы надеемся что все в порядке a+b=0, но это не так
C>        assert a+b=0;//fail
C>        //тут код зависящий от инвареанта
C>        a++;
C>        b--;
C>    }
C>}
C>


Признаться, совсем не понял, какое отношение к локу имеют наши надежды на какое-то состояние каких-то переменных Ну вынесите lock на самый верх, даже совсем уберите — что изменится? Lock вообще-то для сохранения консистентности взаимосвязанных данных в многопоточном окружении, и никак не может препятствовать (как и наоборот) её разрушению нами-же хоть в однопоточном, хоть в многопоточном окружении.
"Нормальные герои всегда идут в обход!"
Re[4]: в чем состоит зло рекурсивных блокировок?
От: sergunok  
Дата: 22.11.10 17:01
Оценка:
Здравствуйте, cvetkov, Вы писали:

C>Здравствуйте, sergunok, Вы писали:


S>>Про консистентное состояние мне неочевидно.


C>
C>int a = 0;
C>int b = 0;
C>void foo(){
C>    lock(this){
C>        assert a+b=0;//ok
C>        int a = newA();
C>        int tmpB = newB();
C>        assert a+tmpB=0;//ok
C>        bar();
C>        b = tmpB;//fail
C>        //а тут мы экспортируем нарушение инварианта наружу.
C>    }
C>}
C>void foo(){
C>    lock(this){
C>        //тут мы надеемся что все в порядке a+b=0, но это не так
C>        assert a+b=0;//fail
C>        //тут код зависящий от инвареанта
C>        a++;
C>        b--;
C>    }
C>}
C>



S>>Чем к примеру плохо вот такое (конкретизирую ваш пример):


C>ничем. никто и не говорил что проблемы будут всегда.

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

Вероятно нужно отдельно рассматривать вопрос через призмы:
— критических секций с т.зр. multi-threading
— транзакций бизнес-логики



Но вот все-таки интересно есть ли проблемы с рекурсивными локами именно с т.зр. multi-thread. Не зря MS рекомендует их не использовать.
Re[5]: в чем состоит зло рекурсивных блокировок?
От: cvetkov  
Дата: 22.11.10 17:09
Оценка:
Здравствуйте, sergunok, Вы писали:

S>Но вот все-таки интересно есть ли проблемы с рекурсивными локами именно с т.зр. multi-thread. Не зря MS рекомендует их не использовать.


с точки зрения именно многопоточности нет.
... << RSDN@Home 1.2.0 alpha 4 rev. 1227>>
Re[5]: в чем состоит зло рекурсивных блокировок?
От: cvetkov  
Дата: 22.11.10 17:09
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Признаться, совсем не понял, какое отношение к локу имеют наши надежды на какое-то состояние каких-то переменных

ну а зачем мы делаем lock если не для поддержания консистентности?
JR> Ну вынесите lock на самый верх, даже совсем уберите — что изменится?
ничего. а вот если бы при рекурсивном вхождении lock бросал эксепшен то можно было бы понять что чтото не так сразу, а не наблюдая последствия разрушения данных.

JR>Lock вообще-то для сохранения консистентности взаимосвязанных данных в многопоточном окружении, и никак не может препятствовать (как и наоборот) её разрушению нами-же хоть в однопоточном, хоть в многопоточном окружении.

спокойно. я тоже за мир во всем мире.
просто попосили объяснить что плохого в рекурсивных блокировках.
... << RSDN@Home 1.2.0 alpha 4 rev. 1227>>
Re[5]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 17:13
Оценка:
Здравствуйте, sergunok, Вы писали:

S>Но вот все-таки интересно есть ли проблемы с рекурсивными локами именно с т.зр. multi-thread. Не зря MS рекомендует их не использовать.


Конечно не зря Прежде всего не следует использовать вложенные блокировки на разных синхрообъектах, так как при этом довольно легко вляпаться как в "live lock", так и в deadlock, причём первое может оказаться не лучше второго. Далее, любой lock по сути — непроизводительные расходы, причём lock в NET почему-то обходится заметно дороже, чем в нативе. А в рекурсивном локе только самый верхний имеет практический смысл, остальные — пустая растрата. Наконец, для повышения параллелизма стоит минимизировать время пребывания в критическом участке, что находится в явном противоречии с рекурсивным локом.
"Нормальные герои всегда идут в обход!"
Re[6]: в чем состоит зло рекурсивных блокировок?
От: cvetkov  
Дата: 22.11.10 17:23
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, sergunok, Вы писали:


S>>Но вот все-таки интересно есть ли проблемы с рекурсивными локами именно с т.зр. multi-thread. Не зря MS рекомендует их не использовать.


JR>Конечно не зря Прежде всего не следует использовать вложенные блокировки на разных синхрообъектах, так как при этом довольно легко вляпаться как в "live lock", так и в deadlock, причём первое может оказаться не лучше второго.

но при этом не будет рекурсивной блокировкой.
JR> Далее, любой lock по сути — непроизводительные расходы, причём lock в NET почему-то обходится заметно дороже, чем в нативе. А в рекурсивном локе только самый верхний имеет практический смысл, остальные — пустая растрата.
т.е. дело только в производительности?
JR> Наконец, для повышения параллелизма стоит минимизировать время пребывания в критическом участке, что находится в явном противоречии с рекурсивным локом.
это как?
... << RSDN@Home 1.2.0 alpha 4 rev. 1227>>
Re[6]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 17:31
Оценка:
Здравствуйте, cvetkov, Вы писали:

C>Здравствуйте, Jolly Roger, Вы писали:


JR>>Признаться, совсем не понял, какое отношение к локу имеют наши надежды на какое-то состояние каких-то переменных

C>ну а зачем мы делаем lock если не для поддержания консистентности?

Ну так я-же сказал:

для сохранения консистентности взаимосвязанных данных в многопоточном окружении

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

JR>> Ну вынесите lock на самый верх, даже совсем уберите — что изменится?

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

Вы не обижайтесь, но если-бы лок был предназначен для таких целей, то он, несомненно поднимал-бы исключение. Но он предназначен для совершенно иных задач

JR>>Lock вообще-то для сохранения консистентности взаимосвязанных данных в многопоточном окружении, и никак не может препятствовать (как и наоборот) её разрушению нами-же хоть в однопоточном, хоть в многопоточном окружении.


C>спокойно. я тоже за мир во всем мире.

C>просто попосили объяснить что плохого в рекурсивных блокировках.

Да я спокоен вобщем-то. Но всё равно приятно, что в вопросах мира у нас с Вами полный консенсус
"Нормальные герои всегда идут в обход!"
Re[7]: в чем состоит зло рекурсивных блокировок?
От: dilmah США  
Дата: 22.11.10 17:37
Оценка: 3 (1)
C>т.е. дело только в производительности?

[quote]
The answer is not efficiency. Non-reentrant mutexes lead to better code.

Example: A::foo() acquires the lock. It then calls B::bar(). This worked fine when you wrote it. But sometime later someone changes B::bar() to call A::baz(), which also acquires the lock.

Well, if you don't have recursive mutexes, this deadlocks. If you do have them, it runs, but it may break. A::foo() may have left the object in an inconsistent state before calling bar(), on the assumption that baz() couldn't get run because it also acquires the mutex. But it probably shouldn't run! The person who wrote A::foo() assumed that nobody could call A::baz() at the same time — that's the entire reason that both of those methods acquired the lock.

The right mental model for using mutexes: The mutex protects an invariant. When the mutex is held, the invariant may not change, but before releasing the mutex, the invariant is re-established. Reentrant locks are dangerous because the second time you acquire the lock you can't be sure the invariant is true any more.
[/quote]
Re[7]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 17:44
Оценка:
Здравствуйте, cvetkov, Вы писали:

C>но при этом не будет рекурсивной блокировкой.


Да. И что?

C>т.е. дело только в производительности?


В первую очередь — да. Вас ведь не смущает рекомендация выносить инвариант за тело цикла? С локом аналогично.

JR>> Наконец, для повышения параллелизма стоит минимизировать время пребывания в критическом участке, что находится в явном противоречии с рекурсивным локом.

C>это как?

А что Вам не понятно? Надо по возможности избавляться от логики, требующей захвата ресурса в цикле, вот и всё
"Нормальные герои всегда идут в обход!"
Re[8]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 18:06
Оценка:
Здравствуйте, dilmah, Вы писали:

Вообще-то эта цитата на тему "реентерабельные vs нереентерабельные" и объяснение, что преимущество нереентерабельных не в их эффективности, а в возможности нагркзить их дополнительным функционалом, отсутствующим у реентерабельных
"Нормальные герои всегда идут в обход!"
Re[5]: в чем состоит зло рекурсивных блокировок?
От: 24  
Дата: 22.11.10 18:22
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Lock вообще-то для сохранения консистентности взаимосвязанных данных в многопоточном окружении, и никак не может препятствовать (как и наоборот) её разрушению нами-же хоть в однопоточном, хоть в многопоточном окружении.


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

Но если лок рекурсивный, то внутри критической секции его можно захватить ещё раз в том же потоке (как писал cvetkov тут http://rsdn.ru/forum/dotnet/4048114.1.aspx
Автор: cvetkov
Дата: 22.11.10
). И при втором захвате мы уже не можем рассчитывать на консистентность данных, т.к. находимся внутри операции, для которой предполагается, что она выполняется атомарно.

Ещё одна проблема хорошо описана тут: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex :

"The biggest of all the big problems with recursive mutexes is that they encourage you to completely lose track of your locking scheme and scope. This is deadly. Evil. It's the "thread eater". You hold locks for the absolutely shortest possible time. Period. Always. If you're calling something with a lock held simply because you don't know it's held, or because you don't know whether the callee needs the mutex, then you're holding it too long. You're aiming a shotgun at your application and pulling the trigger. You presumably started using threads to get concurrency; but you've just PREVENTED concurrency."

Re[9]: в чем состоит зло рекурсивных блокировок?
От: dilmah США  
Дата: 22.11.10 18:22
Оценка:
JR>Вообще-то эта цитата на тему "реентерабельные vs нереентерабельные"

не уловил смысла. Рекурсивные это реентерабельные.

JR> и объяснение, что преимущество нереентерабельных не в их эффективности, а в возможности нагркзить их дополнительным функционалом, отсутствующим у реентерабельных


там объяснение как нужно смотреть идеологически на мутексы -- как на защиту инварианта. Не понял где ты там увидел нагрузку дополнительной функциональностью.
Re[9]: в чем состоит зло рекурсивных блокировок?
От: Sinix  
Дата: 22.11.10 18:28
Оценка:
Здравствуйте, Jolly Roger, Вы писали:


JR>Вообще-то эта цитата на тему "реентерабельные vs нереентерабельные" и объяснение, что преимущество нереентерабельных не в их эффективности, а в возможности нагркзить их дополнительным функционалом, отсутствующим у реентерабельных


На эту тему было много холиваров, но давно. Я придерживаюсь точки зрения Jou Duffy — ре-энтрабельность (в текущем виде) очень легко использовать неправильно. Предпочёл бы, чтобы по умолчанию кидалось исключение, а для ре-энтрабельности требовалось вызывать перегрузку с LockBehavior.AllowReenterancy. И, конечно же, Monitor.NestLevel(lockObj).
Re[6]: в чем состоит зло рекурсивных блокировок?
От: Jolly Roger  
Дата: 22.11.10 18:41
Оценка:
Здравствуйте, 24, Вы писали:

24>Здравствуйте, Jolly Roger, Вы писали:


JR>>Lock вообще-то для сохранения консистентности взаимосвязанных данных в многопоточном окружении, и никак не может препятствовать (как и наоборот) её разрушению нами-же хоть в однопоточном, хоть в многопоточном окружении.


24>Когда пишут лок, то обычно подразумевают, что между его захватом и освобождением у данного участка кода будет монопольный доступ к некоторым ресурсам. Поэтому после входа можно нарушить консистентность, а перед выходом из критической секции её восстановить, т.к. выполнение этого участка атомарно с точки зрения другого потока, и он неконсистентных данных не увидит. Т.е. мы предполагаем, что за пределами критической секции данные непротиворечивы.


24>Но если лок рекурсивный, то внутри критической секции его можно захватить ещё раз в том же потоке (как писал cvetkov тут http://rsdn.ru/forum/dotnet/4048114.1.aspx
Автор: cvetkov
Дата: 22.11.10
). И при втором захвате мы уже не можем рассчитывать на консистентность данных, т.к. находимся внутри операции, для которой предполагается, что она выполняется атомарно.


Я-же говорил уже — уберите отсюда лок, и у Вас абсолютно ничего не изменится.
Ну дак кто Вам виноват, что Вы подразумеваете за локом ту функциональность, которая ему не свойственна Лок предназначен только для одного — он гарантирует, что единовременно данный участок кода будет исполняться только одним потоком. Это всё, на этом его функции заканчиваются. Всё остальное должен обеспечить Ваш алгоритм. И если он таков, что консистентность будет нарушена даже в однопоточном окружении, то лок-то тут при чём?

24>Ещё одна проблема хорошо описана тут: http://stackoverflow.com/questions/187761/recursive-lock-mutex-vs-non-recursive-lock-mutex :


24>

"The biggest of all the big problems with recursive mutexes is that they encourage you to completely lose track of your locking scheme and scope. This is deadly. Evil. It's the "thread eater". You hold locks for the absolutely shortest possible time. Period. Always. If you're calling something with a lock held simply because you don't know it's held, or because you don't know whether the callee needs the mutex, then you're holding it too long. You're aiming a shotgun at your application and pulling the trigger. You presumably started using threads to get concurrency; but you've just PREVENTED concurrency."


Ну а здесь как раз и пишут о вреде параллелизму вследствии слишком длительного захвата. Я об этом уже упоминал.
"Нормальные герои всегда идут в обход!"
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.