Глава.30 .Гибридные.конструкции.синхронизации.потоков
Если бы члены класса
Transaction
были статическими, для их безопасности
в отношении потоков достаточно было бы сделать статическим поле
m_lock
.
Однако я думаю, вы уже поняли из предшествующих обсуждений, что класс
Monitor
не должен быть реализован как статический; его следует реализовать, как
и все прочие конструкции, в виде класса, допускающего создание экземпляров
и вызов экземплярных методов. Также реализация класса
Monitor
как статического
создаст ряд дополнительных проблем:
Если тип объекта-представителя является производным от
System.Marshal-
ByRefObject
, на такой объект может ссылаться переменная (эта тема рассма-
тривалась в главе 22). При передаче методам класса
Monitor
ссылки на такой
представитель блокируется представитель, а не представляемый им объект.
Если поток вызывает метод
Monitor.Enter
и передает в него ссылку на объект-
тип, загруженный нейтрально по отношению к домену (о том, как это сделать,
мы говорили в главе 22), поток блокирует этот тип во всех доменах процесса.
Это известная недоработка CLR, нарушающая декларируемую изолированность
доменов. Исправить ее без потери производительности сложно, поэтому никто
этим не занимается. Пользователям просто рекомендуют никогда не передавать
ссылку на объект-тип в методы класса
Monitor
.
Так как строки допускают интернирование (это обсуждалось в главе 14), два
разных фрагмента кода могут ошибочно сослаться на один и тот же объект
String
в памяти. При передаче ссылки на этот объект в методы типа
Monitor
выполнение
этих двух фрагментов кода будет непреднамеренно синхронизироваться.
При передаче строки через границу домена CLR не создает ее копию; ссылка на
строку просто передается в другой домен. Это повышает производительность,
и в теории все должно быть в порядке, так как объекты типа
String
неизменны.
Но с ними, как и с любыми другими объектами, связан индекс блока синхрони-
зации, который может изменяться. Таким образом, потоки в различных доменах
начинают синхронизироваться друг с другом. Это еще одна недоработка CLR,
связанная с недостаточной изолированностью доменов. Поэтому пользователям
рекомендуется никогда не передавать ссылок на объекты типа
String
методам
класса
Monitor
.
Так как методы класса
Monitor
принимают параметры типа
Object
, передача
им значимого типа приводит к его упаковке. В результате поток блокирует
упакованный объект. При каждом вызове метода
Monitor.Enter
блокируется
другой объект, и синхронизация потоков вообще отсутствует.
Применение к методу атрибута
[MethodImpl(MethodImplOptions.Synchro-
nized)]
заставляет JIT-компилятор окружить машинный код метода вызовами
Monitor.Enter
и
Monitor.Exit
. Если метод является экземплярным, этим мето-
дам передается
this
, что приводит к установлению неявно открытой блокировки.
865
Гибридные.конструкции.в.FCL
В случае статического метода этим двум методам передается ссылка на объект-
тип, что приводит к потенциальной блокировке нейтрального по отношению
к домену типа. Поэтому использовать данный атрибут не рекомендуется.
При вызове конструктора типов (он обсуждался в главе 8) CLR блокирует для
типа объект-тип, гарантируя, что всего один поток примет участие в инициа-
лизации данного объекта и его статических полей. И снова загрузка этого типа
нейтрально по отношению к домену создает проблемы. К примеру, если код
конструктора типа войдет в бесконечный цикл, тип станет непригодным для
использования всеми доменами в процессе. В данном случае рекомендуется по
возможности избегать конструкторов типа или хотя бы делать их как можно
более короткими и простыми.
К сожалению, дальше все становится только хуже. Так как разработчики при-
выкли в одном и том же методе устанавливать блокировку, что-то делать, а затем
снимать блокировку, в C# появился упрощенный синтаксис в виде ключевого слова
lock
. Рассмотрим следующий метод:
private void SomeMethod() {
lock (this) {
// Этот код имеет эксклюзивный доступ к данным...
}
}
Приведенный фрагмент эквивалентен следующему:
private void SomeMethod() {
Boolean lockTaken = false;
try {
//
Monitor.Enter(this, ref lockTaken);
// Этот код имеет монопольный доступ к данным...
}
finally {
if (lockTaken) Monitor.Exit(this);
}
}
Первая проблема в данном случае состоит в принятом разработчиками C# реше-
нии, что метод
Monitor.Exit
лучше вызывать в блоке
finally
. Они считали, что это
гарантирует снятие блокировки вне зависимости от происходящего в блоке
try
. Од-
нако ничего хорошего в этом нет. Если в блоке
try
в процессе изменения состояния
возникнет исключение, состояние окажется поврежденным. И снятие блокировки
в блоке
finally
приведет к тому, что с поврежденным состоянием начнет работать
другой поток. Лучше позволить приложению зависнуть, чем оставить его работать
с поврежденными данными и потенциальными брешами в защите. Кроме того,
вход в блок
try
и выход из блока снижает производительность метода. Некоторые
JIT-компиляторы не поддерживают подстановку для методов, в которых имеются
блоки
try
, что еще больше снижает производительность. В итоге мы получаем
866
Do'stlaringiz bilan baham: |