Что такое тупик?
При написании многопоточных приложений одной из наиболее распространенных проблем являются взаимоблокировки.
Мои вопросы к сообществу:
Что такое тупик?
Как вы их обнаруживаете?
Вы справляетесь с ними?
И, наконец, как вы предотвращаете их появление?
20 ответов
Блокировка происходит, когда несколько процессов пытаются получить доступ к одному и тому же ресурсу одновременно.
Один процесс проигрывает и должен ждать завершения другого.
Взаимная блокировка возникает, когда ожидающий процесс все еще удерживает другой ресурс, который необходим первому, прежде чем он сможет завершиться.
Итак, пример:
Ресурс A и ресурс B используются процессом X и процессом Y
- Х начинает использовать А.
- X и Y пытаются начать использовать B
- Y 'выигрывает' и получает B первым
- теперь Y нужно использовать A
- A заблокирован X, который ждет Y
Лучший способ избежать взаимоблокировок - избегать перекрестного перехода процессов таким образом. Уменьшите необходимость блокировать что-либо как можно больше.
В базах данных избегайте внесения множества изменений в разные таблицы в одной транзакции, избегайте триггеров и по возможности переключайтесь на оптимистическое / грязное /nolock чтение.
Позвольте мне объяснить реальный (не реальный) пример тупиковой ситуации из криминальных фильмов. Представьте, что преступник держит в заложниках, и против этого, полицейский также держит заложника, который является другом преступника. В этом случае преступник не собирается отпускать заложника, если полицейский не отпустит своего друга. Также полицейский не собирается отпускать друга преступника, если только преступник не освободит заложника. Это бесконечная ненадежная ситуация, потому что обе стороны настаивают на первом шаге друг от друга.
Криминальная и полицейская сцена
Проще говоря, когда двум потокам нужны два разных ресурса и каждый из них имеет блокировку ресурса, в которой нуждается другой, это тупик.
Другое объяснение высокого уровня тупика: разбитые сердца
Вы встречаетесь с девушкой, и через день после ссоры обе стороны разбиты друг другу и ждут звонка " Мне жаль, и я пропустил". В этой ситуации обе стороны хотят общаться друг с другом, если и только если одна из них получает вызов " Мне жаль" от другой. Поскольку ни один из них не собирается устанавливать связь и ожидает в пассивном состоянии, оба будут ждать, пока другой установит связь, что приведет к тупиковой ситуации.
Взаимные блокировки возникают только тогда, когда у вас есть два или более замков, которые могут быть обнаружены одновременно, и они захвачены в другом порядке.
Способы избежать тупиков:
- избегать замков (если возможно),
- избегать использования более одного замка
- всегда берите замки в одном и том же порядке.
Чтобы определить тупик, сначала я бы определил процесс.
Процесс : Как мы знаем, процесс - это не что иное, как program
в исполнении.
Ресурс : Для выполнения процесса программы необходимы некоторые ресурсы. Категории ресурсов могут включать память, принтеры, процессоры, открытые файлы, ленточные накопители, компакт-диски и т. Д.
Deadlock : Deadlock - это ситуация или условие, когда два или более процессов удерживают некоторые ресурсы и пытаются получить дополнительные ресурсы, и они не могут освободить ресурсы, пока не завершат выполнение.
Состояние или ситуация тупика
На приведенной выше диаграмме есть два процесса P1 и p2 и два ресурса R1 и R2.
Ресурс R1 выделяется для процесса P1, а ресурс R2 выделяется для процесса p2. Для завершения выполнения процесса P1 необходим ресурс R2, поэтому запрос P1 для R2, но R2 уже выделен для P2.
Точно так же процесс P2 для завершения своего выполнения требует R1, но R1 уже выделен для P1.
оба процесса не могут освободить свой ресурс до тех пор, пока они не завершат свое выполнение. Так что оба ждут других ресурсов и будут ждать вечно. Так что это условие DEADLOCK.
Для возникновения тупика должны быть выполнены четыре условия.
- Взаимное исключение - каждый ресурс либо в настоящее время выделен ровно одному процессу, либо он доступен. (Два процесса не могут одновременно управлять одним и тем же ресурсом или находиться в своем критическом разделе).
- Удерживать и ждать - процессы, в настоящее время удерживающие ресурсы, могут запрашивать новые ресурсы.
- Нет вытеснения - если процесс удерживает ресурс, он не может быть удален другим процессом или ядром.
- Циклическое ожидание - каждый процесс ожидает получения ресурса, который удерживается другим процессом.
и все эти условия выполняются на приведенной выше диаграмме.
Тупик возникает, когда поток ожидает чего-то, что никогда не происходит.
Как правило, это происходит, когда поток ожидает мьютекс или семафор, который никогда не был освобожден предыдущим владельцем.
Это также часто случается, когда вы сталкиваетесь с двумя потоками и двумя блокировками:
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
Обычно вы обнаруживаете их, потому что то, что вы ожидаете, никогда не произойдет, или приложение зависнет полностью.
Вы можете взглянуть на эту замечательную статью в разделе Deadlock. Это в C#, но идея остается той же для другой платформы. Я цитирую здесь для удобства чтения
Блокировка возникает, когда два потока ожидают ресурс, удерживаемый другим, поэтому ни один из них не может продолжить работу. Самый простой способ проиллюстрировать это с помощью двух замков:
object locker1 = new object();
object locker2 = new object();
new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2); // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1); // Deadlock
}
Взаимная блокировка является распространенной проблемой в многопроцессорных / многопрограммных задачах в ОС. Скажем, есть два процесса P1, P2 и два глобально разделяемых ресурса R1, R2, и в критическом разделе должны быть доступны оба ресурса
Первоначально ОС назначает R1 для обработки P1 и R2 для обработки P2. Поскольку оба процесса работают одновременно, они могут начать выполнять свой код, но ПРОБЛЕМА возникает, когда процесс попадает в критическую секцию. Таким образом, процесс R1 будет ожидать, пока процесс P2 не выпустит R2, и наоборот... Таким образом, они будут ждать вечно (СОСТОЯНИЕ ЗАМЯТКИ).
Небольшая АНАЛОГИЯ...
Твоя Мать (О.С.),
Вы (P1),
Твой брат (P2),
Apple, (R1)
Нож (R2),
критическая секция (резка яблока ножом).Твоя мать вначале дает тебе яблоко и нож твоему брату.
Оба счастливы и играют (исполняют свои коды).
Любой из вас хочет порезать яблоко (критический раздел) в какой-то момент.
Вы не хотите давать яблоко своему брату.
Твой брат не хочет дать тебе нож.
Так что вы оба будете ждать очень долго очень долго:)
Контроль параллелизма на основе блокировки
Использование блокировки для управления доступом к общим ресурсам может привести к взаимоблокировкам, и только планировщик транзакций не может предотвратить их возникновение.
Например, системы реляционных баз данных используют различные блокировки, чтобы гарантировать свойства ACID транзакции.
Независимо от того, какую систему реляционной базы данных вы используете, блокировки всегда будут устанавливаться при изменении (например, UPDATE
или DELETE
) определенная запись в таблице. Без блокировки строки, которая была изменена текущей выполняющейся транзакцией, атомарность будет скомпрометирована.
Что такое тупик
Как я объяснил в этой статье, взаимоблокировка возникает, когда две параллельные транзакции не могут выполняться, потому что каждая из них ждет, пока другая снимет блокировку, как показано на следующей диаграмме.
Поскольку обе транзакции находятся в фазе получения блокировки, ни одна из них не снимает блокировку до получения следующей.
Выход из тупиковой ситуации
Если вы используете алгоритм управления параллелизмом, основанный на блокировках, всегда существует риск возникновения тупиковой ситуации. Тупиковые ситуации могут возникать в любой среде параллелизма, а не только в системе баз данных.
Например, многопоточная программа может зайти в тупик, если два или более потока ожидают блокировки, которая была установлена ранее, так что ни один поток не может продвинуться вперед. Если это происходит в приложении Java, JVM не может просто заставить поток остановить выполнение и снять блокировки.
Даже если Thread
класс выставляет stop
метод, этот метод устарел, начиная с Java 1.1, потому что он может привести к тому, что объекты останутся в несогласованном состоянии после остановки потока. Вместо этого Java определяетinterrupt
, который действует как подсказка, поскольку поток, который был прерван, может просто игнорировать прерывание и продолжить его выполнение.
По этой причине приложение Java не может восстановиться из ситуации взаимоблокировки, и разработчик приложения обязан упорядочить запросы на получение блокировки таким образом, чтобы взаимоблокировки никогда не могли возникнуть.
Однако система базы данных не может обеспечить выполнение данного порядка получения блокировок, поскольку невозможно предвидеть, какие еще блокировки потребуется получить в дальнейшем для определенной транзакции. За сохранение порядка блокировок отвечает уровень доступа к данным, и база данных может только помочь в восстановлении после тупиковой ситуации.
Ядро базы данных запускает отдельный процесс, который сканирует текущий граф конфликтов на предмет циклов ожидания-блокировки (которые вызваны тупиками). Когда цикл обнаружен, ядро базы данных выбирает одну транзакцию и прерывает ее, в результате чего ее блокировки снимаются, так что другая транзакция может выполняться.
В отличие от JVM, транзакция базы данных разработана как элементарная единица работы. Следовательно, откат оставляет базу данных в согласованном состоянии.
Для получения дополнительных сведений по этой теме ознакомьтесь также с этой статьей.
Тупиковая ситуация возникает, когда две нити приобретают блокировки, которые препятствуют прогрессу любой из них. Лучший способ избежать их - это осторожное развитие. Многие встроенные системы защищают от них, используя сторожевой таймер (таймер, который сбрасывает систему всякий раз, когда она зависает в течение определенного периода времени).
Взаимная блокировка возникает, когда существует круговая цепочка потоков или процессов, каждый из которых содержит заблокированный ресурс и пытается заблокировать ресурс, удерживаемый следующим элементом в цепочке. Например, два потока, которые удерживают, соответственно, блокируют A и блокируют B, и оба пытаются получить другую блокировку.
Взаимная блокировка может быть определена формально как: Набор процессов блокируется, если каждый процесс в наборе ожидает события, которое может вызвать только другой процесс в наборе. Поскольку все процессы ожидают, ни один из них никогда не вызовет каких-либо событий, которые могли бы разбудить любого из других членов набора, и все процессы продолжают ждать вечно. Здесь ниже приведены четыре условия, которые должны присутствовать для возникновения тупика. Если один из них отсутствует, тупик невозможен.
Условие взаимного исключения: каждый ресурс в настоящее время либо назначен ровно одному процессу, либо доступен.
Условие удержания и ожидания: процессы, в настоящее время удерживающие ресурсы, предоставленные ранее, могут запрашивать новые ресурсы.
Условие исключения: ранее предоставленные ресурсы не могут быть принудительно удалены из процесса. Они должны быть явно освобождены процессом, который их держит.
Условие циклического ожидания. Должна существовать циклическая цепочка из двух или более процессов, каждый из которых ожидает ресурс, удерживаемый следующим членом цепочки. В общем, для борьбы с тупиками используются четыре стратегии. Это: Просто игнорируйте проблему в целом. Может быть, если вы игнорируете это, он будет игнорировать вас. Обнаружение и восстановление. Пусть возникают взаимоблокировки, выявляйте их и принимайте меры. Динамическое избегание путем тщательного распределения ресурсов. Предотвращение: структурным отрицанием одного из четырех условий, необходимых для возникновения тупика.
Классическая и очень простая программа для понимания тупиковой ситуации:-
public class Lazy {
private static boolean initialized = false;
static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println(initialized);
}
}
Когда основной поток вызывает Lazy.main, он проверяет, был ли инициализирован класс Lazy, и начинает инициализировать класс. Теперь основной поток устанавливает значение initialized в значение false, создает и запускает фоновый поток, метод запуска которого устанавливает значение initialized в значение true, и ожидает завершения фонового потока.
На этот раз класс в настоящее время инициализируется другим потоком. При этих обстоятельствах текущий поток, который является фоновым потоком, ожидает объекта Class до завершения инициализации. К сожалению, поток, который выполняет инициализацию, основной поток, ожидает завершения фонового потока. Поскольку два потока теперь ждут друг друга, программа ОТКЛЮЧЕНА.
Допустим, один поток хочет перевести деньги со «Счета A => Счет B», а другой поток хочет перевести деньги с «Счета B => Счет A» одновременно. Это может вызватьdeadlock
.
Первый поток заблокирует «Учетную запись A», а затем он должен заблокировать «Учетную запись B», но не может, потому что второй поток уже заблокировал «Учетную запись B». Точно так же второй поток не может заблокировать учетную запись, потому что он заблокирован первым потоком. поэтому эта транзакция останется незавершенной, и наша система потеряет 2 потока. Чтобы предотвратить это, мы можем добавить правило, которое блокирует записи базы данных в отсортированном порядке. Таким образом, поток должен просмотреть имя или идентификаторы учетных записей и принять решение о блокировке в отсортированном порядке: A => B. Здесь будет состояние гонки, и тот, кто победит, обработает его код, и второй поток возьмет на себя управление. Это решение для данного конкретного случая, но взаимоблокировки могут возникать по многим причинам, поэтому в каждом случае будет свое решение.
ОС имеет механизм обнаружения взаимоблокировок с определенным интервалом времени, и когда она обнаруживает взаимоблокировку, она запускает подход к восстановлению. Подробнее об обнаружении взаимоблокировок
В примере мы потеряли 2 потока, но если мы получим больше взаимоблокировок, эти взаимоблокировки могут вывести систему из строя.
Взаимная блокировка возникает, когда поток ожидает завершения другого потока, и наоборот.
Как избежать?
- Избегайте вложенных замков
- Избегайте ненужных замков
- Используйте поток присоединиться ()
Как вы это обнаружили?
запустите эту команду в cmd:
jcmd $PID Thread.print
ссылка: гиксфоргекс
Тупиковая ситуация - это ситуация, возникающая, когда доступно меньше ресурсов, как того требует другой процесс. Это означает, что, когда количество доступных ресурсов становится меньше, чем запрашивается пользователем, тогда в этот момент процесс переходит в состояние ожидания. Иногда время ожидания увеличивается, и нет никакой возможности выяснить проблему нехватки ресурсов. эта ситуация известна как тупик. На самом деле, взаимоблокировка - это серьезная проблема для нас, и она возникает только в многозадачной операционной системе. Блокировка не может происходить в однозадачной операционной системе, поскольку все ресурсы присутствуют только для той задачи, которая в данный момент выполняется......
Лучший выход из тупика - не попасть в тупик
- системные ресурсы или критическое пространство должны использоваться сбалансированным образом.
- отдавать приоритет ключевым переменным.
- Если будет использоваться более одного объекта блокировки, должен быть указан порядковый номер.
- Тупик возникает в результате некорректного использования объектов синхронизации.
Например, тупик с С#:
public class Deadlock{
static object o1 = new Object();
static object o2 = new Object();
private static void y1()
{
lock (o1)
{
Console.WriteLine("1");
lock (o2)
{
Console.WriteLine("2");
}
}
}
private static void y2()
{
lock (o2)
{
Console.WriteLine("3");
lock (o1)
{
Console.WriteLine("4");
}
}
}
public static void Main(string[] args)
{
Thread thread1 = new Thread(y1);
Thread thread2 = new Thread(y2);
thread1.Start();
thread2.Start();
}
}
Выше некоторые объяснения хороши. Надеюсь, что это также может быть полезно: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
В базе данных, когда сеанс (например, ora) хочет ресурс, удерживаемый другим сеансом (например, данными), но этот сеанс (данные) также хочет ресурс, который удерживается первым сеансом (ora). Также может быть задействовано более двух сессий, но идея будет такой же. Фактически, Deadlocks препятствуют продолжению работы некоторых транзакций. Например: предположим, что ORA-DATA удерживает блокировку A и запрашивает блокировку B, а SKU удерживает блокировку B и запрашивает блокировку A.
Спасибо,
Взаимоблокировки возникают не только с блокировками, хотя это наиболее частая причина. В C++ вы можете создать тупик с двумя потоками и без блокировок, просто заставив каждый поток вызывать join() для объекта std::thread для другого.
Тупик - это состояние системы, в котором ни один процесс / поток не может выполнить действие. Как упоминалось другими, взаимоблокировка обычно является результатом ситуации, когда каждый процесс / поток желает получить блокировку для ресурса, который уже заблокирован другим (или даже тем же) процессом / потоком.
Существуют различные методы, чтобы найти их и избежать их. Человек очень много думает и / или пробует много вещей. Однако общение с параллелизмом общеизвестно сложно, и большинство (если не все) люди не смогут полностью избежать проблем.
Некоторые более формальные методы могут быть полезны, если вы серьезно относитесь к решению подобных проблем. Наиболее практичный метод, который мне известен, - это использование теоретического подхода к процессу. Здесь вы моделируете свою систему на некотором языке процессов (например, CCS, CSP, ACP, mCRL2, LOTOS) и используете доступные инструменты для (моделирования) проверки на наличие взаимоблокировок (и, возможно, некоторых других свойств). Примерами используемых инструментов являются FDR, mCRL2, CADP и Uppaal. Некоторые смелые души могут даже доказать, что их системы зашли в тупик, используя чисто символические методы (доказательство теорем; ищите Овики-Гриза).
Однако эти формальные методы обычно требуют определенных усилий (например, изучение основ теории процессов). Но я думаю, это просто следствие того, что эти проблемы сложны.
Mutex по сути является замком, обеспечивающим защищенный доступ к общим ресурсам. В Linux тип данных мьютекса потока - pthread_mutex_t. Перед использованием инициализируйте его.
Чтобы получить доступ к общим ресурсам, вы должны заблокировать мьютекс. Если мьютекс уже заблокирован, вызов заблокирует поток, пока мьютекс не будет разблокирован. По завершении посещения общих ресурсов их необходимо разблокировать.
В целом, есть несколько неписаных основных принципов:
Получите блокировку перед использованием общих ресурсов.
Удерживать замок как можно быстрее.
Снимите блокировку, если поток возвращает ошибку.