Что такое deadlock и как с ним бороться


Содержание

Deadlocks

Что такое взаимоблокировки и как с ними бороться


Автор: Иван Бодягин
Источник: RSDN Magazine #5-2003

Опубликовано: 05.05.2004
Исправлено: 10.12.2020
Версия текста: 1.0

Введение

Проблема взаимоблокировок в реальном приложении может привести к порче достаточно большого количества нервных клеток, и в то же время довольно скудно описана. Цель данной статьи – хотя бы отчасти восполнить этот досадный пробел и объяснить, что такое взаимоблокировки и как с ними бороться. В качестве подопытной свинки выбран Microsoft SQL Server, однако теоретическая часть также относится и к другим серверам баз данных, хотя бы отчасти применяющим блокировочный механизм для обеспечения корректности параллельной обработки транзакций, например, DB2, Oracle, Informix и даже Interbase.

Основные понятия

Разговор о взаимоблокировках сложно вести, не располагая некоторой базовой терминологией. Здесь я попытаюсь изложить необходимый минимум основных понятий и терминов. Эта часть никоим образом не претендует на полноту, поэтому в случае возникновения каких-либо вопросов рекомендую обратиться к литературе упомянутой ниже. Те же, кто в блокировках, как таковых, разбирается в достаточной степени, могут смело пропустить этот раздел.

Блокировки

Удивительно, но на форумах достаточно часто появляются вопросы, из текста которых становится ясно, что автор попросту перепутал термины «блокировка» (lock) и «взаимоблокировка» (deadlock). Во избежание подобных недоразумений начнем с самого начала.

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

Блокировка не может быть наложена на несколько объектов одновременно. Между наложением блокировок на два разных объекта, теоретически, может произойти что угодно, даже если эти объекты – две записи в одной и той же таблице, расположенные рядом. С помощью блокировок обеспечивается синхронизация доступа к ресурсам. Под ресурсами или объектами здесь и далее будет иметься в виду какой-нибудь объект БД – запись, страница данных или таблица. Синхронизация происходит благодаря тому, что прежде чем прозвести с объектом какие-то действия (прочитать или изменить), на него накладывается блокировка. Она запрещает изменять или даже читать объект другим транзакциям до тех пор, пока транзакция, наложившая блокировку, не завершит работу с этим объектом. Синхронизация доступа нужна для того, чтобы не допустить воздействия одной транзакции на другую при одновременном выполнении. Иными словами, в идеальном случае, транзакция, даже если их одновременно выполняется множество, должна дать такой же результат, как если бы она выполнялась одна, а других транзакций не было вообще. Однако следует помнить, что в большинстве серверов такой идеальный режим работы параллельных транзакций «по умолчанию» не включен. Подробнее об этом чуть ниже.

ПРИМЕЧАНИЕ

Стоит упомянуть, что блокировка – отнюдь не единственный способ обеспечить вышеупомянутую синхронизацию. В теории существует больше десятка способов, как блокировочных, так и не основанных на блокировках, а так же гибридных; версионные (multiversioning), на временных метках (timestamp), на направленных ацикличных графах (DAG), агрессивные и консервативные их варианты, и т.д.

Типы блокировок

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

Read Lock – блокировка чтения, она же «коллективная», она же «разделяемая». Смысл этой блокировки в том, что она совместима с точно такими же блокировками. Иными словами, на один и тот же ресурс может быть наложено сколь угодно много коллективных блокировок. В терминологии MSSQL эта блокировка называется Shared Lock, или сокращенно S.

Write Lock – блокировка записи, она же «монопольная», она же «эксклюзивная». Эта блокировка не совместима ни с Read Lock, ни сама с собой, ни с каким либо другим типом блокировок. То есть в один момент времени на один объект может быть наложена только одна монопольная блокировка. Эта блокировка в терминологии MSSQL называется Exclusive Lock, или же сокращенно X.

Update Lock – это промежуточная блокировка, блокировка «обновления». Она совместима с Read Lock, но не совместима с Write Lock и сама с собой. Иными словами на один объект могут быть одновременно наложены одна блокировка обновления, ни одной монопольной блокировки и сколь угодно много коллективных блокировок. Этот тип блокировок введен как раз для снижения риска возникновения взаимоблокировки. Каким именно образом, будет объяснено ниже.

Блокировки намерения

Вышеописанными тремя типами возможные типы блокировок не ограничиваются. Нас также будут интересовать так называемые «блокировки намерения» (Intent Lock)

Дело в том, что объекты в БД выстраиваются в своеобразную иерархию Таблица->Страница->Запись. Любая из вышеупомянутых блокировок может быть наложена на любой из этих объектов.

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

Из возможных типов блокировок нас интересуют (здесь и далее используется терминология Microsoft SQL Server):

Intent Share – коллективная блокировка намерения, сокращенно IS.

Intent Exclusive – монопольная блокировка намерения, сокращенно IX.

Все упомянутые блокировки вместе образуют так называемую «Матрицу совместимости» (Compatibility Matrix)

IS S U IX X
Intent Shared (IS) Yes Yes Yes Yes No
Shared (S) Yes Yes Yes No No
Update (U) Yes Yes No No No
Intent Exclusive (IX) Yes No No Yes No
Exclusive (X) No No No No No
Таблица 1

В таблице 1 “Yes” означает, что блокировки совместимы, то есть могут быть одновременно наложены на один и тот же объект, а “No” означает, что не совместимы.

Протокол двухфазной блокировки

Важную роль в обеспечении корректной параллельной обработки транзакций играет «протокол двухфазной блокировки» (Two Phase Locking – 2PL). Существует соответствующая теорема, в которой доказывается, что если транзакция удовлетворяет протоколу двухфазной блокировки, то она корректна, то есть результат ее выполнения не зависит от других транзакций.

Суть 2PL в том, что нельзя снять однажды наложенную блокировку до тех пор, пока не наложены все блокировки, необходимые транзакции. Таким образом, работа с блокировками в транзакции делится на две фазы: фаза наложения блокировок и фаза снятия. В практических реализациях, как правило, применяется строгий протокол двухфазной блокировки – Strict 2PL. Его особенность в том, что фаза снятия блокировок наступает после фиксации транзакции.

Уровни изоляции

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

В стандарте ANSI SQL вводятся четыре уровня изоляции. И по названиям, и по поведению уровни изоляции в Microsoft SQL Server полностью соответствуют описанным в стандарте.


В стандарте уровни изоляции привязаны к четырем «феноменам» – нарушениям изолированности транзакций: грязная запись, грязные чтения, неповторяющиеся чтения и фантомы. Повышение уровня изоляции последовательно устраняет эти аномалии одну за другой. Теперь по порядку.

1. Read Uncommitted – самый низкий уровень изоляции. Позволяет читать «грязные» данные незафиксированых транзакций, отсюда и название феномена – «грязное чтение» (Dirty Read). Суть феномена в том, что если первая транзакция запишет какие-то данные, вторая их прочитает, а потом первая транзакция будет отменена, то получится, что вторая транзакция прочитала данные, которые никогда не существовали.

2. Read Committed – при этом уровне изоляции грязное чтение невозможно, то есть второй транзакции не дадут прочитать данные первой до тех пор, пока первая транзакция не зафиксируется. Но при этом уровне изоляции все еще возможна аномалия неповторяющегося чтения. Суть этого феномена в том, что если первая транзакция один раз прочитала данные, а потом вторая их изменила и зафиксировалась, то повторное чтение тех же данных первой транзакцией вернет уже измененные данные.

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

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

Особенности Microsoft SQL Server

Ввиду того, что все практические эксперименты будут проводиться на Microsoft SQL Server 2000, необходимо также описать некоторые особенности внутренней механики этого сервера.

Для работы с блокировками сервер идентифицирует каждый объект. Для этого используются идентификаторы ресурса (Resource) и объекта (ObjId). Для разных типов объектов эти идентификаторы отличаются. Нас будут интересовать следующие объекты:

TAB – таблица. Идентификатор объекта «целое число», например 26173590. Поскольку таблица – объект чисто логический, идентификатора ресурса она не имеет.

PAG – страница данных. Виртуальная страница данных принадлежит конкретной таблице, поэтому идентификатор объекта совпадает с идентификатором таблицы, данные которой размещены на этой странице. В то же время страница имеет физический эквивалент – страницу данных в файле, поэтому имеется также идентификатор ресурса. Он представляет собой комбинацию идентификатора файла данных (fileId) и номера страницы внутри файла (pageId), например, 1: 1723

RID – запись. Виртуальная запись принадлежит странице, поэтому так же, как и в случае со страницей, ObjId совпадает со страничным и, соответственно, табличным идентификаторами. Физический эквивалент также присутствует – это слот в странице данных, соответственно, есть и идентификатор ресурса, который состоит из идентификатора файла данных, идентификатора страницы данных и, наконец, идентификатора записи внутри страницы. Например, 1:1723:2

Чтобы определить, какие блокировки на что наложены в определенный момент времени, существует специальная хранимая процедура sp_Lock, но пользоваться ей не всегда удобно, поскольку выдается много лишней информации, и объекты выводятся без имен. Поэтому я предпочитаю пользоваться вот таким запросом:

Этот запрос вернет все блокировки, наложенные на указанную таблицу, с вот такими данными: SPID – идентификатор пользовательской сессии, DBID – идентификатор базы данных, ObjID – идентификатор объекта, ObjName – имя объекта, Type – тип объекта, Resource – идентификатор ресурса, Mode – тип блокировки, Status – статус блокировки.

Что же касается уровней изоляции, то, как уже было сказано, они полностью соответствуют стандарту ANSI SQL. Следует обратить особое внимание, что уровнем изоляции по умолчанию является READ COMMITTED. Иными словами, если не предпринять ряд специальных усилий, то в некоторых случаях возможны различные нарушения изолированности транзакций.

ПРЕДУПРЕЖДЕНИЕ

Данный раздел является лишь описанием необходимой терминологии и ни в коей мере не претендует на полноту. За более полной информацией рекомендую обратиться к источникам, упомянутым в списке литературы.

Взаимоблокировка

Большинство способов обеспечения параллелизма, хотя бы отчасти основанных на блокировках, подвержено взаимоблокировкам (deadlock). И хотя известны достаточно остроумные алгоритмы, позволяющие не допускать подобных ситуаций в принципе, в коммерческих приложениях они почти не встречаются. Microsoft SQL Server здесь не является исключением, и также подвержен взаимоблокировкам (они же «мертвые блокировки» или «тупиковые ситуации»).

Взаимоблокировка, как можно понять из названия – это ситуация, когда транзакции блокируют друг друга таким образом, что дальнейшее выполнение невозможно. В силу протокола двухфазной блокировки ни одна из участвующих во взаимоблокировке транзакций не может отпустить уже захваченные ей ресурсы до того, как наложит блокировки на все, что ей необходимо. А получить все необходимые ресурсы мешают уже наложенные блокировки. Таким образом, получается замкнутый круг. Естественно, и транзакций, и объектов в общем случае может быть сколь угодно много. Разорвать такую блокировку без внешнего вмешательства невозможно, и если не предпринимать специальных усилий, то транзакции будут находиться в состоянии ожидания бесконечно долго. Разрешить подобную ситуацию можно лишь путем отмены хотя бы одной из транзакций.

Встроенные способы определения взаимоблокировок

Понятно, что доводить до бесконечного ожидания не стоит, и, как правило, в СУБД, в той или иной степени подверженной взаимоблокировкам, присутствуют механизмы определения подобных тупиковых ситуаций и их устранения.

Timeout based

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

Wait-for graph based

Существуют и более удачный способ определения взаимоблокировок (хотя и более трудоемкий). Для этого менеджер блокировок строит направленный граф, который называется «графом ожидания» (wait-for graph). В вершинах этого графа находятся транзакции, а в ребрах – зависимости. Например, ребро Ti->Tj появляется в том случае, если Ti ждет, пока Tj освободит какой-нибудь объект. Таким образом, если в графе ожидания возникает цикл (T1->T2->…->Tn->T1), то T1 ждет сама себя, как и все остальные n транзакций в цикле, следовательно, транзакции заблокированы намертво. В данном случае обнаружение взаимоблокировок сводится к нахождению замкнутых циклов в графе ожидания. Сами зависимости в граф добавляются и уничтожаются по мере получения и снятия блокировок, технически в этом ничего сложного нет. Сложность лишь в том, как часто менеджер блокировок должен проверять граф ожидания на наличие циклов. Теоретически это можно делать каждый раз при добавлении новой зависимости, однако делать проверки так часто слишком накладно, поскольку, как правило, количество обычных блокировок намного выше мертвых, к тому же сама взаимоблокировка никуда не денется и дождется, пока за ней придут. Поэтому проверять наличие циклов можно либо когда в граф добавляется какое-то фиксированное количество граней, либо опять же, по истечении некоего таймаута. Но здесь, в отличие от предыдущего способа, гарантируется, что будет найдена именно мертвая блокировка, а также, что мы обнаружим все мертвые блокировки, а не только те, которые продержались достаточно долго.

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

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

Timestamp based

Существуют механизмы, позволяющие вообще не допускать тупиковых ситуаций при использовании протокола двухфазной блокировки, например, на основе временных меток (timestamp).

ПРИМЕЧАНИЕ

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

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

  1. «ожидание-гибель» (wait-die). Если транзакция T1 «старше» Т2, тогда транзакции Т1 разрешается пребывать в состоянии ожидания на блокировке. Если же Т1 «младше» T2, тогда Т1 откатывается.
  2. «ранение-ожидание» (wound-wait). Если транзакция T1 «старше» T2, тогда T1 «ранит» T2; ранение обычно носит «смертельный» характер – транзакция Т2 откатывается, если только к моменту получения «ранения» T2 не оказывается уже завершенной. В этом случае Т2 «выживает» и отката не происходит. Если же Т1 «младше» Т2, тогда Т1 разрешается находиться в состоянии ожидания на блокировке.


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

Недостаток же этого способа заключается в том, что число откатов здесь гораздо больше, чем в реализации на основе графа ожидания.

Реализация в Microsoft SQL Server

В Microsoft SQL Server используется механизм устранения взаимоблокировок на основе графа ожидания. Граф строится при каждом запросе блокировки. По истечении некоего тайм-аута просыпается монитор блокировок, и если он обнаруживает, что какая-то транзакция ждет слишком долго, инициируется процесс нахождения замкнутого цикла в графе ожидания. В случае обнаружения мертвой блокировки происходит откат одной из транзакций, участвующих в цикле. «Жертва» вычисляется в зависимости от объема проделанной работы, которая в свою очередь определяется по количеству записей в журнале транзакций, которые необходимо откатить. Однако есть возможность указать серверу, какую транзакцию предпочтительнее видеть в качестве «жертвы», с помощью команды:

Здесь @deadlock_var – переменная в диапазоне от 1 до 12, чем меньше число, тем ниже приоритет; LOW соответствует 3, а NORMAL – 6.

Но как бы сервер не старался, все, что он сможет сделать по своей инициативе – это отменить одну из транзакций. Самостоятельно Microsoft SQL Server отмененную транзакцию заново не запускает, а возвращает сообщение об ошибке. Поэтому в клиентском приложении необходимо предусмотреть обработку данной ситуации и, возможно, перезапуск отмененной транзакции. Однако по ряду причин целиком полагаться на обработку подобных ошибок в приложении не следует, это последний рубеж обороны по защите нервов конечного пользователя. Недостатки от перекладывания всей ответственности на клиента в данном случае таковы:

  • Возрастает нагрузка на журнал транзакций, так как каждая неудачная попытка тоже будет приводить к записи в журнале, включая запись об откате. Для интенсивно работающей системы это может быть критично, ввиду того, что самым узким местом в таких случаях часто оказывается именно журнал транзакций.
  • Монитор, отслеживающий замкнутые циклы в графе ожидания, не работает непрерывно, таким образом, взаимоблокировка не обнаруживается мгновенно. Это снижает производительность системы в целом из-за того, что ни в чем неповинные транзакции вынуждены ждать, пока менеджер не отменит одну из намертво заблокированных.
  • Подобное решение в общем случае отвратительно масштабируется, так как исходная причина не устранена, и с увеличением нагрузки число взаимоблокировок будет возрастать, причем не линейно. А следовательно, будет расти и количество ожидающих транзакций, что в итоге может привести к полной неработоспособности системы.

Возможные причины возникновения взаимоблокировок

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

Строго говоря, все случаи взаимоблокировки сводятся к нарушению порядка доступа к объектам. Далее разберем несколько примеров транзакций, потенциально способных привести к тупиковой ситуации. Но перед этим, чтобы примеры были более наглядными, надо выбрать базу для экспериментов (например стандартную Northwind) и создать в ней табличку для дальнейших опытов. Для этого достаточно выполнить в Query Analyzer’е вот такой скрипт:

Первый пример

Самый тривиальный случай, нарушение порядка доступа в чистом виде, представляют собой две транзакции примерно следующего содержания:

Первая транзакция — T1.

Вторая транзакция — T2.

Если эти транзакции стартуют одновременно, то произойдет взаимоблокировка по причине очевидного нарушения порядка доступа. T1 сначала обращается к записи X = 1, а затем к записи X = 3. Т2 же, наоборот, сначала обращается к записи X = 3, а затем к X = 1.

Рисунок 1. Порядок обращения к записям, приводящий к взаимоблокировке.

При одновременном старте Т1 захватывает запись X = 1, в это время Т2 успевает захватить запись X = 3. Затем T1 хочет захватить запись X = 3, но она уже захвачена T2, поэтому T1 ожидает T2 на блокировке, и в граф добавляется ребро T1->T2. Примерно в это же время T2 хочет захватить запись X = 1, которая также уже захвачена T1. В графе ожидания появляется второе ребро T2->T1 и он становится цикличным. Ну а поскольку подобная ситуация без грубого вмешательства неразрешима, то одна из транзакций будет отменена, другая же, пользуясь тем, что блокировка исчезла, спокойно завершит свою работу.

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

Второй пример

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

Очень часто встречается примерно такая последовательность операторов в одной транзакции:

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

Выполним вышеприведенный T-SQL-код из транзакций T1 и T2. Чтобы имитировать возможное развитие событий при параллельной работе будем выполнять транзакции по частям. Сначала половину T1, затем целиком T2, а потом оставшуюся часть T1. Эффект будет точно таким же, как если бы в реальном приложении между двумя операторами T1 успела бы пролезть транзакция T2. На самом деле для получения взаимоблокировки достаточно, чтобы между двумя операторами T1 успел втиснуться только первый оператор T2, дальнейший порядок операций уже не важен.

Итак, выполним первую часть T1 в одном из окон Query Analyser’а:

Все выполнилось успешно, но транзакция все еще считается активной, мы ее не отменили и не зафиксировали. Если в этот момент посмотреть на блокировки, наложенные на таблицу Tbl, можно увидеть примерно следующую картину, с точностью до констант:

Иными словами, мы наложили коллективную блокировку (S) на конкретную запись (RID 1:17495:1), и две коллективные блокировки намерения (IS) выше по иерархии, на страницу и таблицу. Откроем новое соединение с той же базой в новом окне QA и попытаемся выполнить эту же транзакцию целиком:

Блокировок, естественно, добавилось:

Те, что (в моем случае) от sp >

Переключимся обратно в первое окно и попытаемся завершить T1:

Теперь и T1 будет ждать, пока T2 освободит свою коллективную блокировку. Таким образом, транзакции будут ожидать друг друга, цикл в графе ожидания замкнется и, некоторое время спустя, когда менеджер блокировок это обнаружит, одна из транзакций будет отменена. Приложение, запустившее ее, получит сообщение 1205 о взаимоблокировке ( Transaction (Process ID 61) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction) , а другая транзакция завершится успешно.

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

Способы устранения

Поскольку взаимоблокировка произошла из-за того, что транзакции удерживали коллективные блокировки и потом попытались их повысить до эксклюзивных, то, в принципе, помочь избежать неприятностей в данном случае сможет понижение уровня изоляции до READ COMMITED. При этом коллективная блокировка не будет держаться до конца транзакции, а снимется сразу после завершения чтения, а значит, обновить записи ничто не помешает. Но тогда вместо взаимоблокировки мы вполне можем получить неверные данные, так как между SELECT и UPDATE сможет втиснуться другая транзакция, которая изменит Y и данные, полученные SELECT’ на момент UPDATE, окажутся неактуальными, чего в некоторых случаях допускать нельзя.

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


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

Третий пример

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

Подготовим две транзакции в разных окнах QA и, соответственно, в разных подключениях.

Первая транзакция: T1

Стоит заметить, что уровень изоляции в данном случае соответствует уровню изоляции, выставляемому по умолчанию, и делать его ниже крайне не рекомендуется. Оператор WAITFOR нужен для того, чтобы сервер исполнял команды не сразу друг за другом, а с разрывом в 10 секунд, чтобы мы успели переключиться во второе окно и стартовать вторую транзакцию, имитируя тем самым одновременность их выполнения.

Вторая транзакция: T2

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

Запустив T1, а затем, переключившись и запустив T2, мы получим взаимоблокировку. Обратите внимание, что на первый взгляд транзакции вполне безобидны. Более того, условия никак не пересекаются по диапазонам, в первом случае затрагиваются строки X = 4 и X = 6, а во втором X = 2. Можно пойти еще дальше, и изменить в T2 условие таким образом:

Тогда условия выборки не будет пересекаться даже по полям! Но взаимоблокировка все равно произойдет.

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

Определение «виновных» транзакций

Существует возможность заставить сервер выдать более полную информацию об ошибке. Однако не следует этой возможностью злоупотреблять, так как производительность сервера при этом серьезно понижается. Для более тонкой настройки сервер поддерживает флаги трассировки (trace flags). Некоторые из этих флагов предназначены для получения более полной информации об ошибках. Флаги устанавливаются с помощью команды DBCC TRACEON (flag,…), а снимаются, соответственно с помощью DBCC TRACEOFF (flag,…).

Вот краткий перечень флагов, которые могут пригодиться при отлове взаимоблокировок:

  • 1204 – сбор расширенной информации о взаимоблокировке.
  • 3605 – выдача информации в EventLog.
  • 3406 – выдача информации в файл errorlog.
  • -1 – сбор информации изо всех сессий.
  • 1206 – сбор информации не только о блокировках, участвующих во тупиковой ситуации (что делает флаг 1204), но и об остальных блокировках, наложенных заблокированными транзакциями.
  • 1200 – сбор информации о порядке наложения блокировок (недокументированный).

Сейчас нас интересует флаг под номером 1204 – выдача расширенной информации о взаимоблокировке, получить же информацию при выставленном флаге можно двумя способами.


    Запустить SQL Profiler, специальную программу для отслеживания работы сервера, и настроить в ней перехват ошибок (event >
СОВЕТ

Можно также добавить флаг 1206 – в этом случае будет сохраняться информация не только о блокировках, непосредственно приведших к тупиковой ситуации, но и об остальных блокировках, чьими владельцами являются транзакции, вовлеченные в замкнутый цикл.

Итак, сначала установим флаги в одном из окон QA, выполнив следующую команду:

Затем выполним T1 в одном окне, потом переключимся в другое, и выполним T2. После того как случится взаимоблокировка, дополнительную информацию о ней можно будет найти в файле errorlog, и среди прочих данных можно будет наблюдать примерно следующее:

Здесь представлена информация о замкнутом цикле в графе ожидания, в цикл входят 2 узла (Node:1 и Node:2). Информация об узлах следующая: транзакции T2 (Node:1) нужна строка RID 7:1:50:1 для изменения (Requested By:\Mode U), владеет этой транзакцией процесс с идентификатором 51 (Requested By:\SPID:51). Однако на эту строку уже наложена эксклюзивная блокировка (Grant List:\Mode: X) процессом с идентификатором 53 (Grant List:\SPID: 53). Сама блокировка нужна, чтобы выполнить оператор UPDATE (Statement Type: UPDATE). Далее идет текст пакета, в котором блокировка была запрошена. Точно так же описан и второй узел графа, только там нужна строка RID 7:1:50:3, которой владеет транзакция, уже описанная в первом узле.

Отсюда становится понятно, за какие ресурсы передрались эти транзакции. Но зачем при выполнении оператора UPDATE транзакции T1 потребовалась строка, которой уже владеет T2 (как было уже замечено, по условиям они не пересекаются), пока не ясно. И при этом непонятно, почему T2 удерживала блокировку – ведь по идее она должна была отработать и зафиксироваться, или откатиться, но блокировку снять. Поскольку других транзакций в это время не работало, значит, виновата та же T1.

Анализ ситуации

Разобраться в том, что происходит дальше, поможет недокументированный флаг трассировки 1200, который выдает всю последовательность наложения блокировок.

ПРИМЕЧАНИЕ

Вообще-то ту же самую информацию можно получить вполне законным способом, используя все тот же SQL Profiler, но дабы не загромождать описание особенностями работы с этой программой, здесь мы будем использовать недокументированный флаг трассировки.

Запустим первую часть T1 (до WAITFOR), предварительно выставив флаг трассировки, и посмотрим, что за блокировки и в каком порядке накладываются.

Получим примерно следующую картину, с точностью до констант:

Сначала сервер накладывает эксклюзивную блокировку намерения IX на таблицу Tbl (TAB 6:2034106287), поскольку в эту таблицу собирается записывать. Далее накладывается блокировка намерения обновления IU на первую страницу в таблице (PAG 6:1:17495). Поскольку в этой таблице нет индекса, то, чтобы найти нужную запись, необходимо прочитать по очереди все записи, входящие в таблицу, а чтобы прочитать запись, ее надо предварительно заблокировать, что мы и наблюдаем.

Обратите внимание, здесь, считывая запись для проверки, сервер накладывает блокировку обновления. Если запись удовлетворит условию выборки, можно будет сконвертировать блокировку в эксклюзивную, не опасаясь возможной взаимоблокировки. Как только сервер убеждается, что запись не подходит, блокировка снимается. На данном уровне изоляции сервер имеет на это полное право, добиваясь тем самым более высокой степени параллелизма.

Как только находится нужная запись, блокировка на страницу конвертируется в IX:

затем блокировка на запись конвертируется в X:

и производится изменение записи. Блокировка этой записи, естественно, до конца транзакции не снимается.

Поскольку сервер не знает, что он уже выбрал все записи, удовлетворяющие условию, то он продолжает перебирать по очереди все, что осталось, накладывая и снимая соответствующую блокировку на каждую запись. Если после выполнения этой части транзакции посмотреть на наложенные блокировки, то мы увидим эксклюзивную блокировку записи x = 4 (RID 1:17495:3) и эксклюзивные блокировки намерения выше по иерархии, на страницу и таблицу:


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

Теперь переключимся во второе окно и выполним T2, также с выставленным флагом отслеживания порядка наложения блокировок:

Затем переключимся обратно в первое окно и завершим выполнение первой транзакции:

После этого произойдет мертвая блокировка, о чем сервер нам и сообщит.

Переключимся опять в окно с T2 (надеюсь, эти скачки не слишком утомительны) и посмотрим, каков был порядок наложения блокировок в T2:

Начало такое же, как и в T1, что, в общем, закономерно: IX – на таблицу, IU – на страницу. Затем U на первую запись, чтобы затем прочитать и выяснить, подходит она нам или нет. Если не подходит, снимаем блокировку и переходим к следующей записи.

Следующая запись подходит, и начинается тот же процесс, что и в первом запросе T1. Конвертируем блокировку на страницу в IX, на запись – в эксклюзивную (X), и производим обновление. Эта блокировка, как и в T1, не снимается. Опять же, поскольку сервер не знает, что он выбрал все записи, удовлетворяющие условию, указанному в WHERE, то он продолжает перебирать оставшиеся записи по очереди. Вот тут и начинаются отличия: Запись X = 4 (RID 6:1:17495:3) удерживается эксклюзивной блокировкой (X), наложенной T1, ведь T1 мы не зафиксировали. И как только T2 доберется до этой записи, то она будет вынуждена ждать на блокировке до тех пор, пока T1 не отменится или не зафиксируется, так как U и X блокировки не совместимы, что мы и наблюдаем:

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

Теперь переключимся в первое окно к T1 и посмотрим, что происходило после старта второй части этой транзакции:

Запрашивается IU на страницу и U на первую запись, все для того же самого – чтобы убедиться, подходит запись или нет. Первая запись не подходит, и блокировка снимается. А вторая запись уже эксклюзивно заблокирована транзакцией T2, которая успела влезть между двумя Update’ами. Вот тут-то и происходит взаимоблокировка.

Рисунок 2. Взаимоблокировка из-за многократного перебора записей.

Без излишних подробностей можно описать происходящее примерно так:

  1. T1 перебирает все записи по очереди, сначала блокируя их (U), убеждается, что запись не нужна и снимает блокировку, до тех пор пока не найдет нужную (x = 4), после чего, поднимает блокировку до X и производит запись. И, что важно, эта блокировка уже не снимается, а висит до конца транзакции.
  2. T2 делает тоже самое. Она начинает перебирать записи, ставя и снимая блокировки, пока не находит нужную (x = 2). После этого она выполняет те же самые действия, что и первая транзакция — конвертацию блокировки в X, а затем запись. Опять-таки, эта блокировка (X) (и только эта) удерживается до фиксации или отмены T2. После этого перебор записей продолжается, так как не известно, все ли подходящие записи выбраны. Рано или поздно T2 натыкается на запись, уже заблокированную T1 (x=4), и вынуждена ждать либо фиксации, либо отмены T1.
  3. Стартует второй оператор T1, со своим перебором, и натыкается на запись, уже заблокированную эксклюзивно (X) транзакцией T2 (x = 2).

Таким образом, T1 ждет T2, которая ждет T1 – взаимоблокировка.

Примечательно, что если на втором шаге T2 наткнется на запись, заблокированную T1, раньше чем найдет хотя бы одну запись, удовлетворяющую ее условию, то никакой мертвой блокировки не случится, так как все блокировки обновления (U), снимаются тут же, не дожидаясь конца транзакции. А значит, T2 ничего не удерживает, и не сможет выполняться дальше, пока T1 не завершиться.

Убедиться в этом можно, поменяв UPDATE в T2 таким образом:

и запустив заново скрипты. Перебирая записи по очереди, T2 раньше доберется до x=4, чем до x=10, и не сможет заблокировать x=10, а будет ждать, пока освободится x=4.

С другой стороны, если и T1 и T2 нашли нужную запись и успешно сконвертировали блокировку, то, что найдет раньше второй оператор в T1, уже значения не имеет. Он все равно вынужден перебрать все записи, и рано или поздно вынужден будет ожидать T2, которая уже ждет T1.

Способы устранения

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

  1. Повысить уровень изоляции до SERIALIZABLE. При этом уровне изоляции все блокировки держатся до конца транзакции, таким образом, первый оператор T1 заблокирует до фиксации или отмены даже те записи, которые отбирались просто для проверки и под условие поиска не подпадали. Транзакция T2 будет вынуждена ждать в самом начале, не успев наложить ни одной блокировки, и не сможет помешать второму обновлению T1. А значит, сначала отработает T1 целиком, а потом уже T2.
  2. С помощью специальной подсказки (hint) указать оптимизатору, что при обновлении записи блокировка должна производиться не по записям, а потаблично. Эффект будет тем же самым, что и при повышении уровня изоляции до SERIALIZABLE. T1 при первом же обращении заблокирует всю таблицу и будет удерживать блокировку до фиксации или отмены, а T2 не сможет захватить ни одну запись до тех пор, пока не отработает T1.
  3. Построить индекс по X. В этом случае не будет никакой необходимости перебирать все записи по очереди. Пользуясь информацией из индекса, транзакции будут сразу обращаться к нужной записи. Таким образом, T1 и T2 никогда не понадобится одна и та же запись, и они не передерутся за ресурсы.

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

Распределенная взаимоблокировка

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

1. Часть графа ожидания находится в клиентском приложении. Предположим, что группе клиентских потоков необходимо синхронизировать доступ к какому-либо ресурсу помимо СУБД. Один поток может захватить клиентский объект и ожидать снятия блокировки в БД. В это время другой поток, захвативший объект БД, необходимый первому потоку, может ожидать, пока первый поток освободит клиентский объект. Главная неприятность подобной взаимоблокировки в том, что она в принципе не детектируется, и приложение повисает намертво, если время ожидания блокировки не выставлено в разумных пределах. Можно порекомендовать следующие методы борьбы:

  • Использовать одно подключение к базе для всех потоков. В этом случае потоки на сервере не будут блокировать друг друга.
  • Microsoft SQL Server поддерживает механизм «связанных подключений» (BoundConnections), когда несколько подключений на клиенте «связываются» вместе и воспринимаются сервером как одно подключение. Эффект тот же самый, что и в предыдущем случае – потоки не блокируют друг друга при доступе к объектам СУБД.
  • Для синхронизации доступа к клиентскому объекту использовать механизм блокировок сервера. Некоторые серверы, в том числе и Microsoft SQL Server, имеют возможность предоставить свой менеджер блокировок для нужд внешних приложений. В этом случае весь граф ожидания находится на сервере, и определить взаимоблокировку не составляет никакого труда.

2. Часть графа ожидания находится на другом сервере баз данных. Тут, как ни странно, все еще сложнее. Для начала можно вспомнить, что не все серверы используют блокировки для синхронизации доступа. Тем не менее, даже если все участники транзакции – серверы, использующие блокировки, но разных производителей, все равно не обойтись без стандартного представления графа ожидания, который понимали бы все СУБД. Такового на данный момент не существует. И, наконец, даже если речь идёт только об одном сервере, то объём информации о графах ожидания, который нужно передавать по сети, может быть довольно значительным, хотя теоретически, в этом случае обнаружить взаимоблокировку можно. Можно было бы также построить механизм борьбы с распределенными взаимоблокировками на основе временных меток. Тогда объем информации, необходимый для предотвращения взаимоблокировки, был бы значительно меньше, но в случае большого числа откатов этот способ малоприменим. На данный момент Microsoft SQL Server не поддерживает определение распределенных взаимоблокировок между различными серверами.

Общие рекомендации

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

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

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

Очень часто в различных performance tips рекомендуют везде, где только можно, устанавливать уровень изоляции в READ UNCOMMITED. Я бы хотел предостеречь от этого шага. Вероятность мертвой блокировки при использовании этого уровня изоляции, конечно, понизится, но риск привести базу в несогласованное состояние при этом возрастает многократно. Бороться с последствиями этого эффекта гораздо сложнее, чем с последствиями возникновения взаимоблокировки. В подавляющем большинстве случаев можно найти выход из ситуации, не используя этот уровень изоляции. Как правило, необходимость его использования является следствием ошибок проектирования.


Довольно часто рекомендуется использовать наиболее низкий уровень изоляции. По сути своей совет правильный, так как при этом уменьшается число ожидающих на блокировках транзакций, уменьшается время удерживания блокировок и, как следствие, уменьшается вероятность того, что различные транзакции схлестнутся в борьбе за ресурсы. Но, как было показано в предыдущем примере, возможны и исключения. Там взаимоблокировка произойдет при уровнях изоляции READ COMMITED и REPEATABLE READ, а при уровне изоляции SERIALIZABLE ее уже не случится. То же самое относится и к рекомендации блокировать как можно меньший объем. В большинстве случаев это действительно помогает, по тем же самым причинам: потенциально опасные транзакции с меньшей вероятностью захватят общие ресурсы, но опять-таки возможны исключения.

Вообще же тут все зависит от конкретной ситуации. Для поиска причин необходимо анализировать errorlog и данные Profler’а, смотреть, на каких ресурсах произошла взаимоблокировка, какие операции «передрались» между собой. Даже если истинная причина не будет найдена, или из-за какой-либо особенности приложения полностью устранить подобную ситуацию будет невозможно, тщательный анализ поможет найти способ существенно снизить возможность возникновения взаимоблокировки. Исходить нужно из того, что любая взаимоблокировка не является таинственным природным феноменом, а имеет свою причину. Эту причину необходимо найти и попробовать устранить. И только если окажется, что причина непонятна, трудно устранима или взаимоблокировка случается крайне редко, можно рассматривать повторное выполнение отмененной транзакции как окончательный вариант.

Первоисточники

[1] Системы Баз Данных, полный курс. Г. Гарсиа-Молино, Дж. Ульман, Дж. Уидом.

[2] Concurrency control and recovery in database systems. Philip A. Bernstein, Vassos Hadzilacos, Nathan Goodman.

[3] Введение в системы баз данных. К. Дж. Дейт

[4] Inside Microsoft SQL Server 2000. Kalen Delaney

[5] Microsoft Books OnLine (Документация к Microsoft SQL Server)

Как избавиться от deadlock при использовании mutex?

В первом потоке так:

Проблема в том, что эти мьютексы находятся в разных классах, скажем Parent и Child , они вызывают методы друг друга, но ничего не знают о внутреннем устройстве друг друга (но если надо, их можно научить, но только как?). Мьютексы защищают какие-то внутренние данные соответствующих классов.

Это происходит при вложенных вызовах:

Callstack — thread 1

Callstack — thread 2

Может есть какой-то паттерн для такой проблемы.

3 ответа 3

Чтобы избавиться от дедлоков, можно использовать функцию std::lock . Она принимает несколько мьютексов, и лочит их с помощью специального алгоритма который позволяет избежать дедлоков.

Применять ее следует таким образом:

Или наоборот, сначала создать lock_guard , а потом залочить:

Обновление: В вашем случае, когда у каждого класса свой мьютекс, и проблема лишь в порядке вызовов, я бы очень не рекомендовал вызывать чужой код под блокировкой. Именно потому, что этот код имеет полное право залочить какой-то другой мьютекс. Вызов чужого кода под залоченным мьютексом — практически всегда опасность deadlock’а.

Попробуйте реорганизовать код так, чтобы он выглядел следующим образом:

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

Часто проблема решается таким образом: мьютексы перенумеровываются, и если код хочет взять несколько мьютексов, он обязан брать их в порядке возрастания номеров.

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

Также, вы можете попытаться избежать проблемы двойного мьютекса тем, что разделите работу с блокируемыми объектами на части/сущности: сначала залочите один мьютекс и извлечёте нужные данные, затем отпустите этот мьютекс и залочите другой, используя при работе со вторым объектом локально сохранённые данные.

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

Резюме: общего решения не существует. Выкручивайтесь.

Бесплатные программы на все случаи жизни

DeadLock – странная утилита для разблокирования заблокированных Windows файлов

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

Установка утилиты проходит по стандартному сценарию, где вначале можно выбрать язык, на котором будет проходить процесс инсталляции (русский присутствует), потом подтверждаем лицензионное соглашение, читаем изменения в последнем релизе программы, можем выбрать папку, где будут располагаться файлы приложения, и разрешить создать иконку для запуска на «Рабочем столе». После чего придется подождать пару секунд, пока все распакуется, и DeadLock будет полностью готов к работе.

Учтите, у вас на компьютере должна быть установлена среда исполнения не ниже .NET Framework 4.6.1. Если у пользователей Windows 10 с этим не должно возникнуть проблем (обычно здесь .NET Framework автоматически обновляется до более новых версий), то другим будет предложено установить её. Это сводиться к тому, что автоматически откроется страница в браузере, откуда вы сможете скачать инсталляционный файл .NET Framework, который приодеться вручную запустить.

Работа DeadLock заключается в том, что пользователи добавляют заблокированные файлы и папки в программу, с которыми вы ничего не можете сделать с помощью обычных файловых менеджеров. После чего из меню, которое вызывается по правой кнопки мыши или раздела «Правки» основного меню, могут копировать перемещать, удалять или очищать содержимое.

Правда как показала практика заставить нормально работать DeadLock непростая задача, во всяком случае, на Windows 7. Вся проблема оказалось в том, что программа работает только с правами администратора, а если их нет, радостно сообщает об этом при старте. Когда вы добавляете проблемный файл или папку через меню проводника и чрез автозапуск, DeadLock запускаться с обычными правами и ничего с ними сделать не может. Поэтому приходиться идти обходным путем, вначале запускать утилиту от администратора, в открывшемся окне добавлять наружные файлы и папки. Опять здесь есть нюансы, перетаскивание нужный файлов в окно программы не работает, все приходиться делать вручную через меню файлы, тратя кучу времени.

Что блокирует конкретный файлы можно увидеть, щелкнув по нему мышью, после чего в нижней таблице появиться список из номера процесса, его названия и исполняемого файла с ним связанного. Плюс около всех папок и файлов отображается их статус, то есть, заблокирован или нет, и разрешил ли пользователь DeadLock работать с ними.


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

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

Программа работает на 32-х и 64-х битных версиях операционных систем. Интерфейс переведен на несколько десятков языков, в том числе и русский.

Страница для бесплатного скачивания DeadLock — https://codedead.com/?page_ >

Размер программы: установочный файл 5,38Мб

Совместимость: Windows Vista, Windows 7, 8 и 10

Что такое дедлок?

Дедлок — это взаимная блокировка нескольких потоков или процессов, ожидающих высвобождения занятых друг другом ресурсов.

Конечно, дедлок относится к тем понятиям, которые лучше изучать теоретически, а не на практике. И если вы до сих пор не знаете, что такое дедлок, надо думать, вы никогда с ним и не сталкивались (хотя, конечно, не факт), и я вам от души желаю, чтобы и дальше вы с ним не столкнулись.

Дедлок (англ. deadlock, что на русский можно перевести как «мёртвая блокировка», хотя встречается и вариант термина «взаимная блокировка») — это ситуация, когда несколько процессов или потоков, взаимно заблокировав друг для друга доступ к некому общему для них всех ресурсу, бесконечно долго ожидают его освобождения. Такая ситуация чаще всего возникает при работе с базами данных, хотя может сложиться и в многопоточном приложении.

Простейшая ситуация, приводящая к deadlock’у, такова: у нас есть два процесса (или потока) и два ресурса, доступ к которым каждый из процессов намерен захватить. Первый процесс захватывает один ресурс, второй — оставшийся, и, выполнив какие-то действия с этими ресурсами, процессы начинают ожидать, когда освободится второй из нужных для них ресурсов. Конечно, вы можете сказать, что такой проблемы не было бы, если бы процесс сначала освобождал ресурс, а потом уже ожидал освобождения другого ресурса. Однако, к сожалению, не всё так просто, и далеко не всегда это возможно. Например, вполне может сложиться ситуация, когда каждому из процессов необходим эксклюзивный доступ к каждому их ресурсов для завершения выполнения какой-либо операции. Стоит заметить, что процессов/потоков, участвующих в дедлоке, может быть и не два, а значительно больше — тогда говорят о кольцевой блокировке.

Дедлок — особенно неприятная ситуация по той причине, что её довольно трудно воспроизвести, что называется, в лабораторных условиях, то есть при отладке приложения. Соответственно, довольно трудно исправить такую ошибку в программе, которая приводит её к дедлоку. Поэтому методы устранения дедлоков носят превентивный характер и используются уже на этапе проектирования программного продукта. Для того, чтобы предотвратить дедлок, стараются исключить одно из условий его возникновения, то есть, например, освобождать одни ресурсы до начала ожидания предоставления доступа к другим, либо же реализуют специальные менеджеры ресурсов, следящие за тем, чтобы не возникало дедлоков, и принудительно устраняющие их. Во многих современных СУБД есть такие менеджеры ресурсов.

Mitsubishi Lancer Ralliart Рыжая Бестия › Бортжурнал › Штатная система «Мёртвый замок» (Deadlock System)

Начну с того, что это довольно редкая система и ставится только лишь в одной стране Европы — Англии. На сколько мне известно, ни в Америке ни даже в родной Японии эта система на Лансерах почему-то не доступна (не говоря о России).

Суть системы состоит в том, что помимо обычного зарытия, эти замки могут заблокироваться так, что их нельзя будет открыть даже изнутри автомобиля, то есть блокиратор (чёрная/хром «щеколда» на внутренней ручке) — не работает! Другими словами — даже если разбить стекло, то дверь открыть невозможно! Разблокировка происходит ТОЛЬКО штатной системой дистанционного управления замками — Keyless Entry System (было у меня с завода) либо Keyless Operation System (то что я поставил сам — www.drive2.ru/cars/mitsub…rnal/4062246863888413930/).

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

Ввиду того что в Англии «праворукие» автомобили, то и замки соответственно заточены на то, что водительская дверь с правой стороны:) Значит это то, что если замки закрыты, то когда водитель (сидящий справа) тянет свою ручку находясь в автомобиле, то дверь открывается автоматически (не надо трогать блокиратор) — точно так происходит и на наших водительских дверях с завода. В добавок, если дверь открыта то нельзя подвинуть блокиратор в стостояние закрыто и захлопнуть дверь.

Менять замки надо во всех 4х дверях, но я пока начал с передних, вот как разительно по габаритам «передние» замки отличаются от наших обычных:

Работает система интересно — при двойном нажатии кнопки закрытия на ключе ( как я уже писал выше) слышится дополнительный «щёлк» в двери и блокиратор при попытке его перевода в состояние «открыто» ПОДПРУЖИНЕННО ВОЗВРАЩАЕТСЯ в состояние «закрыто» и даже если его рукой удержать в положении открыто и потянуть внутреннюю ручку — это ничего не даст!

В теории эту систему можно легко приладить к любой другой нештатной охранной системе, поскольку искомые блокировки замков управляются подачей импульса на дополнительно проложенные в каждую дверь провода. Эти провода соединяются все 4 в одном месте в итоге получается всего один провод на который подаётся масса после обычного закрытия замков. Например, если есть иммобилайзер работающий по метке — можно сделать так, чтобы при исчезновении метки из зоны опроса — система срабатывала.
Разблокировка замков происходит автоматически с обычным сигналом открытия исходящим от ключа с кнопками.

Вот фото заднего замочка с данной системой:

На фото хорошо виден рычажок системы «детский замок».

Megano Blog

пятница, 2 августа 2013 г.

Advanced Техники Помогающие Избегать и Находить Deadlocks в .NET Приложениях.

Advanced Техники Помогающие Избегать и Находить Deadlocks в .NET Приложениях.

Автор статьи: Joe Duffy

Автор перевода: Александр Кобелев

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

За основу взята статья Advanced Techniques To Avoid And Detect Deadlocks In .NET Apps. Однако, переведена лишь ее теоритическая часть, оставшаяся часть (использующая C++ и CLR hosting API) доступна в оригинальной статье, вместо нее, вам будет предложена пошаговая инструкция по обнаружению deadlocks без использования C++.

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

Эта статья рассматривает:

  • Причины возникновения deadlocks
  • Методику lock leveling, помогающую избегать deadlocks
  • Обнаружение deadlocks и их устранение
  • Обзор CLR hosting API для подключения и обнаружения deadlocks

Для статьи доступен исходный код: Deadlocks.exe(188 KB) (оригинальный исходный код разработанный под Visual Studio 2005)


Зависшее приложение это одна из самых неприятных ситуаций, с которой пользователь может столкнуться. Их ужасно трудно найти при тестировании перед поставкой приложения, и еще труднее отлаживать после развертывания приложения. В отличие от необработанного исключения, зависание не создает crash dump, и не приводит к срабатыванию специальной логики (например, уведомление разработчика о произошедшей ошибке). Пользователи часто завершают процессы зависших приложений, до того как вы будете в силах получить дамп памяти, для последующего анализа и локализации проблемы.

Зависания могут быть как периодические, так и постоянные, они могут возникнуть как результат медленных Input/Output операций, или при обработке сложных / долгих алгоритмов, так и при взаимоисключающем доступе к ресурсу. В любом случае это снижает отзывчивость приложения и может привести к подвисаниям. К примеру: код, который блокирует выполнение GUI потока, может помешать текущей обработке пользовательского ввода и обработке системных событий, в результате приложение подвиснет и система отобразит его как «Not Responding». Даже программы без графического интерфейса могут страдать от проблем отзывчивости, при использовании общих ресурсов или при выполнении Inter-Thread, Inter-Process или сетевых взаимодействий. Самый худший тип зависания — когда приложение подвисает намертво и никогда не возобновит своей работы, другими словами deadlock.

Разработка приложений с большой конкуренцией, используя стандартные Windows® средства — потоки и блокировки, это задача, гораздо труднее, чем может показаться на первый взгляд. Стоит всего лишь забыть заблокировать данные доступные нескольким потокам и это может привести к необработанному исключению (в лучшем случае), или к повреждению данных.

Существует компромисс между coarse-grained locking (крупнозернистость, http://design-pattern.ru/patterns/coarse-grained-lock.html , плюсы: простота и стойкость данных, минусы: снижает возможность применения параллелизма) и fine-grained locking (мелкозернистость, плюсы: высокая возможность параллелизма; минусы: требует более продуманной архитектуры, большое количество блокировок, больше предрасположено к появлению ошибок). Сегменты кода различной «зернистости», каждый раз, при получении и освобождении блокировок склонны взаимодействовать друг с другом непредсказуемым образом. Если ваша система ничего не делает для сдерживания deadlocks, то даже самые незначительные ошибки, при проектировании блокировок, склонны мешать взаимодействию внутри программы.

Сегодняшние модели программирования, требуют, чтобы программист думал о том, что в течение выполнения программы, методы могут выполняться не по порядку. Когда программа начинает вести себя непредсказуемо, то бывает чрезвычайно сложно воспроизвести шаги, приведшие к инциденту. Deadlocks, более заметны, чем гонка методов (в простейшем случае программа просто повиснет), но так же сложны в локализации проблемы и в устранении. Вы можете найти deadlocks в процессе выполнения тестов, но наиболее вероятно, что большинство из них будет дремать до поставки продукта, и впоследствии будут «радовать» ничего не подозревающего пользователя.

Вы можете бороться с deadlocks, используя комбинацию известных практик и агрессивного тестирования на различных hardware платформах. Самая распространенная из этих дисциплин и является темой данной статьи. Но сначала рассмотрим основы deadlocks.

Deadlocks 101

Deadlock возникает, когда несколько конкурирующих потоков ждут один другого для возобновления своей работы. Если это звучит как парадокс, то так оно и есть.

Существует четыре основных условия, которые должны совпасть, чтобы произошел deadlock:

Система должна поддерживать Mutual Exclusion (Взаимоисключение)

Когда один поток завладел общим ресурсом, другой поток не может завладеть им. Это относится не только к большинству критических секций, но так же и к GUI в Windows. Каждое окно принадлежит одному потоку, который несет полную ответственность за обработку входящих сообщений. Ошибки с Mutual Exclusion при проектировании, могут привести к потере отзывчивости приложения (в лучшем случае) или к deadlock.

Поток уже владеющий, каким то ресурсом, должен иметь возможность впадать в неограниченное ожидание. Например, когда поток, вошедший в критическую секцию, имеет возможность завладеть еще одной критической секцией, и если она занята — впасть в ожидание. Это вполне ожидаемый результат, если вторая критическая секция удерживается другим потоком.

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

Условие замкнутого ожидания. Замкнутое ожидание происходит в случае, если цепочка из двух или более потоков ждут освобождения ресурсов заблокированных потоками, находящимися в этой цепочке. Также это относится к одному единственному потоку, если он пытается заблокировать ресурс, который невозможно повторно заблокировать, в этом случае поток сам себе создаст deadlock. Однако большинство ресурсов можно заблокировать повторно, что ликвидирует возможность deadlock в одном потоке.

Любой программист, работавший с пессимистичными алгоритмами ( http://ru.wikipedia.org/wiki/Блокировка_(СУБД) ) должен понимать как происходят deadlocks. Если пессимистичный алгоритм при попытке доступа обнаруживает, что ресурс уже занят, то он переходит в ожидание, и ждет до тех пор, пока ресурс не станет свободным (к примеру, блокировки). В сравнении с оптимистичными алгоритмами, пытающимися выполнить работу с риском возникновения разногласия данных, которые будут выявлены позже, например, при фиксации транзакции. Пессимистичный алгоритм много легче реализовать, потому что он более распространен и уже встроен в платформы, по сравнению с оптимистичными технологиями, как правило пессимистичный алгоритм принимает форму монитора (в C# конструкция lock; Visual Basic® SyncLock) mutex ( http://ru.wikipedia.org/wiki/Мьютекс ) или Win32® CRITICAL_SECTION.

Lock-free ( http://ru.wikipedia.org/wiki/Неблокирующая_синхронизация ) алгоритмы, способные обнаружить и реагировать на конкуренцию за ресурсы, являются довольно распространенными для программного обеспечения системного уровня. Эти алгоритмы часто избегают совместного входа в критическую секцию, выбирая livelock вместо deadlock. Livelock тоже представляет проблему для параллельного кода, однако она вызвана fine-grained конкурентностью. Результат Livelock — программа работает, но вхолостую. Проиллюстрировать эффект можно следующим образом — Двое встречаются лицом к лицу. Каждый из них пытается обойти другого, но сдвигаются постоянно в одну и ту же сторону, и никто из них не может пройти вперед.

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

  1. Поток 1 блокирует ресурс А
  2. Поток 2 блокирует ресурс В
  3. Поток 1 пытается заблокировать ресурс В, но он уже заблокирован потоком 2 и поток 1 впадает в ожидание, пока ресурс В не освободится
  4. Поток 2 пытается заблокировать ресурс А, но он уже заблокирован потоком 1 и поток 2 впадает в ожидание, пока ресурс А не освободится

В этом случае потоки заблокируются и никогда не проснутся. Следующий C# код демонстрирует данную ситуацию:

Иллюстрация 2 демонстрирует 6 возможных вариантов гонки потоков между двумя потоками. Только в 2 случаях (1 и 4) не наблюдается deadlock. Очень заманчиво сделать вывод, что deadlock произойдет два раза из трех (по тому, что 4 варианта из 6 ведут к deadlock), но это не так, почти всегда будет срабатывать первый вариант, следующим по статистике четвертый и лишь за тем 2,3,5,6 так как временное окно, необходимое для блокировки сразу двух объектов очень мало (в начале LockA и сразу за этим, без какой-либо другой работы, захватить LockB, или наоборот LockB и сразу же LockA), и вероятность того что потоки стартуют одновременно тоже очень мала.

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

В приведенном выше примере deadlock достаточно легко идентифицировать и исправить, для этого нужно переписать методы t1 и t2 так, чтобы они захватывали и освобождали блокировки в одном и том же порядке. К примеру с начала всегда захватывается A и лишь за тем B:

Таким образом, мы избавляемся от так называемого «deadly embrace» (смертельное объятие), если всегда захватывать и освобождать ресурсы в одном и том же порядке — deadlock невозможен. Но давайте рассмотрим вариант метода, где возможен deadlock, но он не так очевиден на первый взгляд:

Предположим, кто то пытается перевести $500 с аккаунта #1234 на аккаунт #5678 и в это же время, кто-то другой пытается перевести $1000 с аккаунта #5678 на аккаунт #1234, в этом случае появляются все условия для создания deadlock. Такая неоднозначность с аргументами (как показано выше), когда множество потоков могут передавать одни те же объекты в разные аргументы, может вызвать большую головную боль. К сожалению эта ситуация является очень распространенной, вызовы виртуальных методов, реализованных в пользовательском коде, так же могут произвести серию вызовов, которая может обзавестись блокировками в непредсказуемом порядке. Непредусмотренная комбинация блокировок постоянно рискует появлением deadlock.

Несколько Коварных Примеров Deadlock

Риск возникновения deadlock не ограничивается только взаимоисключающими критическими секциями. Есть и более коварные пути, на которых deadlock может возникнуть в вашей программе. Soft deadlock – когда кажется, что в вашем приложении произошел deadlock, но на самом деле программа просто застряла на выполнении операции с большим временем ожидания или возникли проблемы, мешающие продолжению работы метода. Например, интенсивные алгоритмы, выполняемые в GUI потоке, могут привести к полной потери отзывчивости приложения. В данном случае использования пула потоков было бы лучшим выбором (предпочтительно использовать новый компонент BackgroundWorker появившийся в .NET Framework 2.0).

Очень важно понимание, как скажется на производительности вызов того или иного метода в вашей программе, но на практике это оказывается очень сложным. Операция, которая вызывает другую, чья производительность имеет широкий диапазон времени выполнения, может привести к непредсказуемым задержкам, блокировкам и нагрузкам на процессор. Ситуация становится хуже, когда вызывающий метод до этого заблокировал какой либо общий ресурс, в этом случае, даже очень быстрые методы, в которых предусмотрен доступ до этого ресурса, будут ждать его освобождения. Части вашего приложения, имеющие превосходную производительность в обычных сценариях, могут ее лишиться, к примеру, если что либо произойдет в системном окружении. Сетевой обмен данными – прекрасный пример, обычно он имеет прекрасную производительность, но как только сетевого канала начинает нехватать для всех приложений запущенных на компьютере или сетевое подключение становится недоступным, производительность методов, использующих сетевой обмен, резко падает. Если вы разрабатывали приложения, использующие сетевой обмен, вы наверняка очень тщательно тестировали этот момент, к примеру, как ваше приложение поведет себя, когда вы отключите сетевой кабель от компьютера.

Когда ваш код выполняется в single-threaded apartment (STA http://www.introligator.org/articles/3/84 http://www.rsdn.ru/article/com/apartmnt.xml ) это эквивалентно эксклюзивной блокировке. Только один поток может обновлять GUI окна или выполнять код внутри STA апартамента. Такие потоки владеют очередью сообщений, в которую кладут информацию другие части приложения для последующей обработки. GUI используют эту очередь для получения информации, например запрос на перерисовку или запрос на закрытие окна. COM прокси используют очередь сообщений для вызова методов объектов, принадлежащих апартаментам. Любой код, выполняющийся в STA, ответственен за постоянный сбор и выполнение запросов из очереди сообщений, в противном случае очередь может засориться, что приведет к потере оперативности. В терминах Win32 это означает использование MsgWaitForSingleObject, MsgWaitForMultipleObjects (и их эквиваленты) или CoWaitForMultipleHandles APIs. Другие варианты, такие как WaitForSingleObject или WaitForMultipleObjects (и их эквиваленты) не будут прокачивать входящие сообщения.

Другими словами STA “lock” может быть освобожден только прокачкой очереди сообщений. Приложения, выполняющие операции с непредсказуемой производительностью в GUI потоке, без прокачки сообщений (как было замечено ранее), могут легко привести к deadlock. Хорошо написанные программы производят такую долгую обработку либо где то в другом месте, либо прокачивают сообщения каждый раз перед блокировкой, чтобы избежать проблем с отзывчивостью. К счастью CLR в управляемом коде прокачивает сообщения для вас (через вызовы Monitor.Enter, WaitHandle.WaitOne, FileStream.EndRead, Thread.Join, и так далее) помогая смягчить эту проблему.

Рассмотрим классический пример STA-induced deadlock. Поток работающий в STA апартаментах генерирует большое количество объектов и автоматически для них генерируются Runtime Callable Wrappers (RCWs http://msdn.microsoft.com/ru-ru/library/8bwh56xe.aspx ). Конечно RCWs должны быть утилизированы когда они становятся недоступны, в противном случае произойдет утечка памяти. Но утилизирующий поток CLR так же вынужден использовать прокси к STA чтобы освободить RCWs. Если STA не прокачивает очередь сообщений (к примеру поток заблокирован методами WaitForSingleObject или WaitForMultipleObjects ), то утилизирующий поток застрянет. Если STA перестанет прокачивать, это скажется на утилизирующем потоке, он будет замедляться и медленно наращивать количество ресурсов, которое ему необходимо утилизировать. Это в свою очередь может привести к out-of-memory и приложение упадет, или оно может перезапуститься (например, ASP.Net). Очевидно, что оба этих результата неудовлетворительны.

Фреймворки высокого уровня, такие как Windows Forms, Windows Presentation Foundation и COM скрывают от пользователя большую часть сложностей при работе с STA, однако они все же могут ошибиться и повести себя непредсказуемо, deadlock один из возможных результатов. Такие ошибки могут легко проскочить через этап тестирования приложения и возникнуть только при стрессовых нагрузках.

Различные типы deadlock, к сожалению, требуют различных методов борьбы. Остальная часть статьи будет посвящена исключительно deadlock критической секции. В CLR 2.0 появилась полезная утилита для отлавливания и отладки проблем связанных с STA. Новый Managed Debugging Assistant (MDA), ContextSwitchDeadlock был создан для отслеживания deadlocks включая межапартаментные переходы (cross-Apartment transitions). Если переход занимает более 60 секунд, CLR предполагает что произошел deadlock и натравливает на него MDA. Для более точной информации как включить и работать с MDA смотрите MSDN документацию.

Существует две основные стратегии, полезные при работе с deadlock критической секции.

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


Выявление и подавление deadlocks. Многие системы баз данных используют эту технику для пользовательских транзакций. Обнаружить deadlock не так сложно, гораздо сложнее правильно на него ответить. Вообще говоря, система обнаружения выбирает жертву (к примеру, один из потоков) и заставляет его произвести отмену текущей операции и сбросить все блокировки. Такая методика в управляемом коде может привести к нестабильности приложения, поэтому должна применяться с большой осторожностью.

Многие разработчики знают о возникновении возможности deadlock на теоретическом уровне, но очень не многие знают, как реально с ними бороться. Давайте взглянем на некоторые решения, которые попадают в обе описанные выше категории.

Избежание deadlocks с техникой Lock Leveling

Довольно распространенный подход для борьбы с deadlock в больших программных системах является техника под названием Lock Leveling (так же известная как Lock Hierarchy или Lock Ordering). Основная стратегия данного подхода заключается в том, что все блокировки имеют некий числовой уровень, так же этот числовой уровень зависит от архитектурного слоя, и после приобретения потоком блокировки он может блокировать ресурсы только более низкого уровня. Например, мы можем назначить общему ресурсу А уровень 10, а общему ресурсу Б уровень 5. Поток, вполне легально может сначала захватить ресурс А, а затем ресурс Б, но если в обратном порядке (так как уровень ресурса А (10) больше чем уровень ресурса Б (5)) произойдет исключение. Такая методика исключает возможность возникновения deadlock

Большое количество современных программных систем используют архитектурные слои (GUI, Business Logic, DAL).

И каждый нижележащий слой ничего не должен знать о вышележащем (к примеру, DAL ничего не знает о Business Logic и не может запускать его методов и так далее, но бывает это правило нарушается). Для того чтобы воспользоваться методикой Lock Leveling нам необходимо так же разделить на логические слои и числовые уровни блокировок, с верху вниз. Предположим в нашем приложении, должно хватить десяти заблокированных ресурсов, на слой, в одном (любом) методе, для его успешного выполнения. GUI — самый верхний слой, его числовые уровни блокировок будут в диапазоне от 30 до 21, Business Logic 20-11, DAL 10-1.

Deadlock: пленум ВС пояснил, когда корпоративный конфликт ведет к ликвидации фирмы

В ходе прошедшего на прошлой неделе Пленума Верховного суда РФ обсудили проект постановления о применении части первой Гражданского кодекса (ГК). Наряду со множеством спорных вопросов правоприменения, возникших в связи с последними поправками в ГК, в объёмном документе – 136 пунктов на 47 страницах – коснулись и конфликтов, парализующих работу компании – так называемых «дедлоках». О том, что делать в этой ситуации и поможет ли суд выйти из управленческого тупика – в материале Право.Ru.

Как зайти в тупик

Что происходит с компанией, где каждый из двух владельцев имеет 50 % доли в уставном капитале и разные мнения по поводу стратегии построения светлого будущего фирмы? Тупик в управлении корпорацией, или так называемый «дедлок» – достаточно частая ситуация для корпоративного права, самым банальным разрешением которой становится, пожалуй, разделение бизнеса – каждый остается при своём.

Возникают управленческие тупики, как правило, от недальновидности – бизнес-партнеры, как супруги, нередко рассчитывают, что серьёзных разногласий удастся избежать, и сторонятся «брачного контракта» – корпоративного договора, в котором оговаривался бы порядок действий в сложной ситуации и варианты выхода из бизнеса. И хотя предвидеть возможный конфликт всегда проще, чем пытаться разрешить его в его острой фазе, часто право остается единственным средством решения проблемы.

Если друг оказался вдруг…

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

«Русская рулетка» (russian roulette) – при возникновении тупиковой ситуации каждый из участников корпоративного договора имеет право направить другому участнику предложение о выкупе половины уставного капитала с указанием цены. Другой участник, соответственно, может либо продать свою долю по названной цене, либо потребовать, чтобы по той же самой цене доля была приобретена у него.

«Техасская перестрелка» (texas shoot-out) – каждая из сторон направляет независимому медиатору запечатанное предложение цены, по которой она готова приобрести долю другой стороны. Конверты вскрываются одновременно, после чего выигрывает заявка с наибольшей ценой. Лицо, подавшее такую заявку, обязано купить, а другая сторона – продать свои акции по указанной цене.

«Голландский аукцион» (dutch auction) – вариант техасской перестрелки. Стороны в предложениях указывают минимальную цену, за которую они готовы продать свою долю в компании. Сторона, предложившая большую цену, получает право выкупить долю другой стороны по цене, указанной в ее предложении.

Оставить дело на суд

Для случаев, когда корпоративного контракта стороны не заключали, есть другие возможности разрешения конфликта.

Во-первых, участник может выйти из общества, получив стоимость своей доли – правда, только если подобный вариант предусмотрен уставом компании. И это, пожалуй, самый неосложнённый сценарий.

По поводу других возможных действий – исключении участника из общества и ликвидации фирмы – высказались в Верховном Суде РФ (с полным текстом постановления Пленума можно ознакомиться здесь >>). При «дедлоке» возможно предъявление требования о ликвидации компании, правда сам конфликт должен быть таким, при котором «злоупотребления допускались всеми участниками» фирмы (п. 29), сказано в постановлении. Одновременно при такой ситуации запрещено удовлетворение иска одного из участников об исключении другого (п. 35).

Исключение участника из общества в случае, если развитию конфликта способствовали действия участника корпорации, после 1 сентября 2014 года стало возможна не только для ООО, но и для непубличного акционерного общества.

В обзоре практики рассмотрения арбитражными судами споров, связанных с исключением участника из общества с ограниченной ответственностью, сделанном в Информационном письме Президиума ВАС РФ от 24.05.2012 N 151, указывалось, что необходимым условием для этого должны являться такие действия, которые причинили обществу значительный вред и (или) сделали невозможной деятельность общества либо существенно ее затруднили, повлекли для него заведомо значительные неблагоприятные последствия. В свою очередь, ВАС РФ было установлено, что исключение из общества с ограниченной ответственностью участника, обладающего долей в размере более 50 % уставного капитала общества, возможно только если участники общества в соответствии с его уставом не имеют права свободного выхода из общества, напоминает Павел Герасимов, партнёр юридического бюро «Падва и Эпштейн».

«При рассмотрении вопросов об исключении участника из общества, многое зависит от толкования тех или иных действий участников, признавать их существенными или нет, т.е. в итоге – от внутреннего убеждения суда. Это является наиболее сложным предметом доказывания по такого рода корпоративным конфликтам», – отмечает Герасимов.

Пожалуй, самые значительные проблемы вызывает попытка ликвидации компании – случай, достаточно часто встречающийся в зарубежной практике. При этом оставшееся после расчета с кредиторами имущество делят между участниками, отмечает Forbes. Соответствующая возможность в российском праве прописана в подп. 5 п. 3 ст. 61 ГК РФ.

Однако на деле это не всегда оборачивается благом: «Норма закона о ликвидации компании по иску учредителя (участника), в случаях, предусмотренных пп.5 п.3 ст. 61 ГК РФ, одновременно является и инструментом разрешения сложных корпоративных конфликтов и лазейкой для недобросовестного давления на бизнес со стороны его участников, – комментирует ситуацию заместитель руководителя Московской коллеги адвокатов «Талион» адвокат Александр Гурин. По его словам, указанная норма применяется нечасто, так как воспользоваться ей можно только в крайнем случае, когда все иные способы разрешения конфликта исчерпаны или их применение невозможно.

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

Пример безрезультативной попытки воспользоваться данной нормой – дело №А40-168939/14, рассмотренное в АС г.Москвы и обжалованное в 9 ААС, напоминает Гурин. В рамках дела истец намеревался добиться ликвидации компании ОАО «ОРГПРИМТВЕРДОСПЛАВ» на основании того, что ОАО не выполняет обязательства перед акционерами и третьими лицами. Однако вскоре стало очевидно, что просто так суд заявленные требования не удовлетворит: необходимо представить исчерпывающие доказательства невозможности дальнейшего сотрудничества. 9 ААС в удовлетворении иска о ликвидации общества и назначении ликвидатора отказал, сочтя, что истец не представил никаких доказательств наличия условий, предусмотренных пп.5 п. 3 ст. 61 ГК РФ – общество являлось фактически действующим юридическим лицом, а факт платежеспособности общества истец не отрицал.. Также суд не обнаружил в действиях общества неустранимых нарушений действующего законодательства.
Как отмечает Гурин, Пункт 29 Проекта Постановления пленума ВС РФ «О некоторых вопросах применения общих положений части первой Гражданского кодекса Российской Федерации» указывает на особенности применения нормы, ограничивая возможности иного её толкования.

В целом, хотя обращение в суд остается крайним способом разрешения конфликта в патовых ситуациях, когда существует прямая угроза деятельности компании, способ этот часто неэффективен, констатирует Евгений Корчаго, председатель коллегии адвокатов «Старинский, Корчаго и партнеры». «Суды в подобных конфликтах как правило не могут принять решение в чью-либо пользу», – полагает он.

По словам Евгения Корчаго, типичным исходом «тупиковых» конфликтов становилось фактическое прекращение бизнес-процессов в компании, при том, что de jure юридическое лицо продолжало существовать. «В подобной ситуации возможность принятия судом решения о принудительной ликвидации таких организаций, выглядит рациональной инициативой. Немаловажным в ситуациях таких конфликтов является установление запрета на удовлетворение иска одного из участников на исключение другого. Данный запрет позволит если не исключить, то максимально ограничить возможные злоупотребления со стороны недобросовестных участников корпоративного конфликта», – отмечает адвокат.

Напомним, что по итогам заседания проект постановления Пленума отправили на доработку. Ожидается, что он будет принят до конца июня.

Deadlock: корпоративное соглашение как способ избежать конфликта

Статьи по теме

Понятие deadlock обозначает конфликтную ситуацию, когда учредители юридического лица корпоративного типа, обладающие равным количеством голосов, не могут договориться между собой.

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

Зарубежная практика предотвращения deadlock-a


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

42 полезных документа для юриста компании

Договор может предусматривать создание сторонами специального выборного органа для урегулирования разногласий «dispute review panel», использование процедуры медиации, привлечение независимого эксперта.

На случай, если deadlock не удастся урегулировать и одному из участников придется покинуть общество, в договоре устанавливают специальные последствия. В частности, сторонам предоставляют право требовать принятия решения о ликвидации общества, право требовать продажи акций другой стороны договора («put/call option»), принудительного выкупа акций другой стороны договора в соответствии с процедурой выкупа, регламентированной договором.

Shotgun-условия

Shotgun-условия – это способы выхода из ситуации, которые можно закрепить в корпоративном соглашении. Международная практика к ним относит следующие договоренности:

  • «Русская рулетка» (russian roulette): при возникновении тупиковой ситуации каждый из участников корпоративного договора имеет право направить другому участнику предложение о выкупе половины уставного капитала с указанием цены. Другой участник может либо продать свою долю по названной цене, либо потребовать, чтобы по той же самой цене доля была приобретена у него.
  • «Техасская перестрелка» (texas shoot-out): каждая из сторон направляет независимому медиатору запечатанное предложение цены, по которой она готова приобрести долю другой стороны. Конверты вскрываются одновременно, после чего выигрывает заявка с наибольшей ценой. Лицо, подавшее такую заявку, обязано купить, а другая сторона – продать свои акции по указанной цене.
  • «Голландский аукцион» (dutch auction): стороны в предложениях указывают минимальную цену, за которую они готовы продать свою долю в компании. Сторона, предложившая наибольшую цену, получает право выкупить долю другой стороны по цене, указанной в ее предложении.

Стоит отметить, что большинство правовых систем стран континентальной Европы заимствовали подобные инструменты (shotgun provisions), успешно применяемые в странах англо-американского права. Так, в 2009 году Суд апелляционной инстанции в Вене утвердил правомерность включения «deadlock clause» (оговорка о дедлоке) в устав закрытого общества и в реестр компаний. Сторона определила выкупную цену акций без учета коммерческой или балансовой стоимости. При этом стороны предусмотрели право участнику B самому приобрести акции A, если он находит такую выкупную цену слишком низкой. Как указал суд, такой механизм основывается на системе «сдержек и противовесов», препятствуя А получить несправедливые преимущества (Appeals Court of Vienna, 20.4.2009 – 28 R 53/09h, GesRZ 2009, 376).

Во всех странах условием применения таких механизмов является честность и добросовестность. Как отмечено в одном из решений, принятом судьей Истребуком (Judge Easterbook), возможность того, что стоимость, названная стороной, может оказаться как ценой покупки, так и ценой продажи, заставляет инициатора процедуры действовать честно (295 F.3d 666. John F. VALINOTE, v. Stephen R. BALLIS. №. 01-3768).

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

Из практики. Обязательство о раскрытии информации сформулировал в 2002 году один из судов в США. Участник, который владел долей 50 процентов и одновременно был руководителем общества, приобрел такую же долю другого участника. Среди активов, которыми владело общество, было здание в Нью-Йорке. Спустя две недели после приобретения доли участник общества, он же руководитель, продал это здание по цене, которая на 250 процентов превышала покупную стоимость доли участия. Суд констатировал, что управляющий участник нарушил свои фидуциарные права, поскольку ему следовало информировать продавца акций о подлинной ценности активов общества.

Система «сдержек и противовесов» действует не во всех случаях применения Shotgun–условий. В частности, в ситуации, когда сторона B, которая получает предложение о покупке акций, но не обладает активами, которые позволят ей приобрести долю стороны А, имеет место злоупотребление правом. В таком случае сторона А может занизить выкупную стоимость. Компании A и B обладают неравными возможностями, чем пользуется А.

Из практики. Практика борьбы с подобными злоупотреблениями, сложившаяся в США, весьма неоднозначна. Так, в одном из решений суд Техаса установил, что сторона А предложила выкупить акции В по цене ниже рыночной стоимости, зная, что B нуждается в средствах. Суд отменил продажу акций участником B (Johnson v. Buck, 540 S.W.2d 393, 411 (Tex. App. 1976)). Напротив, в похожей ситуации суд Миннесоты не усмотрел нарушения фидуциарных обязанностей. По мнению суда, сложно представить, как предложение приобрести акции, сделанное в соответствии с корпоративным соглашением, может нарушить фидуциарные обязанности (Larken Minnesota, Inc. v. Wray, 881 F.Supp. 1413, 1421 (D.Minn.1995)).

Согласно решению суда Огайо, ведение переговоров на условиях корпоративного соглашения нарушает фидуциарные обязанности, если целью участника является получение несправедливых преимуществ (Schaefer v. RMS Realty, 741 N.E. 2d 155, 175 f. (Ohio Ct. App. 2000)).

Несколько иначе подходят к схожим ситуациям наука и практика ФРГ. По мнению специалистов, в отсутствие специального соглашения трудно доказать, что на стороне А лежит обязанность заявить справедливую цену. Такие фидуциарные обязанности чрезмерны. Однако средства защиты можно использовать, если существует большой разрыв между предложенной ценой и действительной стоимостью акций. Занижение цены стороной А, которая знает о финансовых трудностях стороны B, может привести к применению судом принципа добросовестности.

Российская практика применения Shotgun–условий

Плюсы и минусы Shotgun–условий

Преимущества Shotgun–условий

1) Этот механизм простой и удобный, если подробно прописать его в корпоративном договоре;

2) в отличие от переговорного процесса, где остаются противоборствующие стороны, сторона запускает процедуру выкупа акций (доли) в одностороннем порядке;

3) можно определить справедливую цену доли участия, поскольку в будущем предлагающая сторона может оказаться как продавцом, так и покупателем;

4) экономический эффект процедуры: не требуется участие внешнего оценщика.

Недостатки Shotgun–условий

1) Нельзя предсказать результат, поскольку при намерении продать долю и выйти из общества сторона конфликта может оказаться единоличным владельцем;

2) возникает угроза для бизнеса, если общество покидает участник, обладающий ноу-хау, деловой репутацией или активом, необходимым для работы общества.

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

Java / Взаимная блокировка или Deadlock.

Автор: rustock ← к списку ← →

Deadlock, он же взаимная блокировка, явление при котором все потоки находятся в режиме ожидания. Чтобы уменьшить шанс появления deadlock’a не рекомендуется использовать методы wait() и notify().

Пример: поток 1 захватывает ресурс A, поток 2 — ресурс B. Потоку 1 из ресурса А нужно захватить ресурс В, который удерживается потоком 2, а потоку 2, наоборот, нужно из ресурса В вызвать ресурс А, который удерживается потоком 1. Получается такая ситуация, что оба потока ждут, пока ресурсы станут доступны. Других потоков в системе нет и они оба бесконечно ждут. Это и называется Deadlock.
Синонимом Deadlock’a называют Livelock, когда все потоки занимаются бесполезной работой и состояние системы не меняется с течением времени.

Если Вам понравился вопрос, проголосуйте за него

Что такое deadlock? Значение термина дедлок

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

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

Илон Маск рекомендует:  Что такое код ncurses_resetty
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL