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





замок возникает, когда несколько процессов пытаются получить доступ к одному и тому же ресурсу одновременно.
Один процесс проигрывает и должен ждать завершения другого.
тупик возникает, когда ожидающий процесс все еще удерживает другой ресурс, который нужен первому, прежде чем он сможет закончить.
Итак, пример:
Ресурс A и ресурс B используются процессом X и процессом Y
Лучший способ избежать взаимоблокировок - избежать подобного перехода процессов. Уменьшите необходимость блокировать что-либо, насколько это возможно.
В базах данных избегайте внесения большого количества изменений в разные таблицы за одну транзакцию, избегайте триггеров и как можно больше переключайтесь на оптимистичное / грязное / запретное чтение.
Я использую здесь процесс как обобщение, а не конкретно процесс ОС. Это могут быть потоки, но также могут быть совершенно разные приложения или соединения с базой данных. Схема такая же.
Привет, учитывая этот сценарий: поток A блокирует ресурс A и имеет длинный процесс. Поток B ожидает блокировки ресурса A. Использование времени ЦП: 20%, можете ли вы считать это тупиковой ситуацией?
@rickyProgrammer нет, это просто обычное ожидание блокировки, хотя разница немного академическая. Ожидание B на медленной скорости A - это блокировка, B ожидание A, ожидание B - это тупиковая ситуация.
Таким образом, взаимоблокировка - это два процесса с заблокированными ресурсами, ожидающими освобождения этих ресурсов.
@rickyProgrammer - это блокировка, которая не освободится, сколько бы вы ни ждали, из-за циклической очереди.
@Keith, вау, я понял, поэтому я попытался перефразировать данный сценарий, сделав его ситуацией тупика: поток A блокирует ресурс A и ожидает блокировки ресурса B. Поток B блокирует ресурс B и ожидает блокировки ресурса A. Я верю в это сейчас это тупик?
Почему это два процесса, которые зависят друг от друга и имеют независимый доступ к общим ресурсам? Ваш пример помог мне понять, что такое тупик, но этот пример кажется несколько сюрреалистичным. Может быть, эти взаимоблокировки часто возникают при работе с большими приложениями и взаимодействиями с базами данных в основном, или когда есть большой персонал, который не синхронизирован и плохо знает уже существующие процессы?
@Lealo, они не зависят друг от друга - они могут быть не связаны или копировать одно и то же. В ответ я пытаюсь быть абстрактным, но они могут возникать даже в довольно простых на вид системах. Единственный критерий - наличие нескольких общих ресурсов - я даже видел, как конкурирующие аспиранты зашли в тупик из-за чрезвычайно редких книг, а взаимоблокировки могут возникать в трафике постоянно. Чем больше и сложнее ваша система, тем чаще они могут возникать. В основном: будьте предельно осторожны, когда вы блокируете какой-либо ресурс, и избегайте одновременной блокировки нескольких ресурсов, насколько это возможно.
Моя ошибка в том, что процессы зависимы. С точки зрения объектно-ориентированного и событийного программирования - как я могу получить общие ресурсы и даже «несколько общих ресурсов»?
@Lealo зависит от того, что вы делаете, но файлы, сетевые сокеты, базы данных, любой внешний ресурс могут стать проблемой. Если вы хотите отправить данные во что-то (записать в файл, обновить таблицу и т. д.), Вам часто нужно сначала заблокировать его или заблокировать, пока вы это делаете. Если ваше приложение не имеет общих, блокируемых / записываемых ресурсов, вам, вероятно, не стоит об этом беспокоиться.
У вас может быть один класс, который выполняет всю обработку внешних данных. Обычно у меня есть класс, который выполняет сериализацию в моих программах, но причина этого в том, что я хотел избежать повторного набора, когда знал, что мне все равно понадобится эта функциональность много раз. Однако это также приводит к тому, что я могу легко управлять любым риском взаимоблокировки при обработке файлов. Я предполагаю, что могу просто объявить методы в этом «синхронизированном». На мой взгляд, тот факт, что потокам нужны одни и те же данные (таким образом, они получают их дважды), это просто плохое программирование. У меня нет опыта работы с сокетами, да и с базами данных не очень много.
@Lealo: да, вы можете направлять все через одну очередь - это один из способов минимизировать тупиковые ситуации, но это также и узкое место. Если у вас нет параллелизма, то нет и взаимоблокировок, но, поскольку в среднем телефоне было несколько процессоров, что не является обычным вариантом использования. Не уверен, что вы имеете в виду с битом «Потокам нужны одни и те же данные», но в любом случае параллелизма (потоки, процессы, машины) вам нужно как-то разделить некоторые данные между ними, либо с помощью копирования, либо с помощью указателей, а сравнение этих вариантов использования слишком велико для этих комментариев.
Я говорю о перспективах разработки Java-программ и не вижу причин, по которым я мог бы попасть в тупиковые ситуации. Если поток X хочет: A и B. Y хочет A и B. У меня будет пакет данных C, содержащий (A и B), и я бы использовал только один поток, чтобы получить его, Thread Z. Нет смысла получения других данных. Потоки X и Y должны иметь разную функциональность, что говорит о том, что им также нужны разные данные. Если у вас должны быть данные в A: s и B: отдельно, это должно быть потому, что у вас есть третий поток, принимающий только A: s и B.s. Реструктурируйте свои объекты.
@Lealo извини, ты пытаешься рассказать мне, как избежать тупиковых ситуаций? Это здорово, но это не совсем то, о чем этот ответ - здесь я просто пытаюсь помочь OP определить, что такое тупик. Если вы хотите обсудить свои стратегии, чтобы избежать их на Java, может быть, это должен быть отдельный вопрос? Тупиковых ситуаций легко избежать, если вы знаете о них и разрабатываете для проблемы, в основном они возникают из-за того, что что-то было упущено - что-то, о чем нужно знать во время проектирования, и что-то искать в ошибках. Как только вы знаете, что происходит тупик, их обычно довольно легко исправить.
Нет, я просто пытался понять ваш пример и чему-то научиться из него (и концептуальной проблемы тупиков). Спасибо за ваше время и усилия, потраченные на этот ответ.
Тупиковая ситуация возникает, когда два потока получают блокировки, которые препятствуют выполнению любого из них. Лучший способ избежать их - тщательная разработка. Многие встроенные системы защищаются от них, используя сторожевой таймер (таймер, который сбрасывает систему всякий раз, когда она зависает в течение определенного периода времени).
Тупиковая ситуация возникает, когда поток ожидает чего-то, чего никогда не происходит.
Обычно это происходит, когда поток ожидает мьютекса или семафора, который никогда не был освобожден предыдущим владельцем.
Это также часто происходит, когда у вас есть ситуация с двумя потоками и двумя блокировками, подобными этой:
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
Обычно вы их обнаруживаете, потому что то, что вы ожидаете, никогда не произойдет, или приложение полностью зависнет.
Тупик происходит, когда поток ожидает чего-то, что произойдет не можешь.
Взаимоблокировки возникают только тогда, когда у вас есть два или более блокировок, которые могут быть задействованы одновременно, и они захватываются в разном порядке.
Способы избежать взаимоблокировок:
Третий пункт по предотвращению тупика (всегда снимайте блокировки в одном и том же порядке) жизненно важен, о чем довольно легко забыть в практике кодирования.
Тупиковая ситуация - это состояние системы, в котором ни один процесс / поток не может выполнить действие. Как упоминалось другими, взаимоблокировка обычно является результатом ситуации, когда каждый процесс / поток желает получить блокировку для ресурса, который уже заблокирован другим (или даже тем же) процессом / потоком.
Существуют различные способы их найти и избежать. Один очень много думает и / или много чего пробует. Однако иметь дело с параллелизмом, как известно, сложно, и большинство (если не все) люди не смогут полностью избежать проблем.
Некоторые более формальные методы могут быть полезны, если вы серьезно относитесь к решению подобных проблем. Самый практичный метод, который мне известен, - это использование теоретико-процессуального подхода. Здесь вы моделируете свою систему на каком-либо языке процесса (например, CCS, CSP, ACP, mCRL2, LOTOS) и используете доступные инструменты для (модели-) проверки на взаимоблокировки (и, возможно, некоторых других свойств). Примеры используемых инструментов: FDR, mCRL2, CADP и Uppaal. Некоторые смельчаки могут даже доказать, что их системы свободны от тупика, используя чисто символические методы (доказательство теорем; ищите Owicki-Gries).
Однако эти формальные методы обычно требуют определенных усилий (например, изучение основ теории процессов). Но я полагаю, что это просто следствие того, что эти проблемы сложны.
Вы можете взглянуть на этот замечательные статьи в разделе Тупик. Он написан на C#, но идея осталась прежней для другой платформы. Я цитирую здесь для удобства чтения
A deadlock happens when two threads each wait for a resource held by the other, so neither can proceed. The easiest way to illustrate this is with two locks:
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
}
По сути, мьютекс - это блокировка, обеспечивающая защищенный доступ к общим ресурсам. В Linux тип данных мьютекса потока - pthread_mutex_t. Перед использованием инициализируйте его.
Чтобы получить доступ к общим ресурсам, вы должны заблокировать мьютекс. Если мьютекс уже заблокирован, вызов заблокирует поток до тех пор, пока мьютекс не будет разблокирован. По завершении посещения общих ресурсов вам необходимо их разблокировать.
В целом, есть несколько неписаных основных принципов:
Получите блокировку перед использованием общих ресурсов.
Удерживайте замок как можно короче.
Снимите блокировку, если поток возвращает ошибку.
Это описывает блокировку, а не тупик.
Тупиковая ситуация возникает, когда существует круговая цепочка потоков или процессов, каждый из которых удерживает заблокированный ресурс и пытается заблокировать ресурс, удерживаемый следующим элементом в цепочке. Например, два потока, которые удерживают соответственно блокировку A и блокировку B, и оба пытаются получить другую блокировку.
Я голосую за вас. Ваш ответ более краток, чем приведенный выше, потому что они заставляют сбивать с толку процесс или поток. Кто-то говорит процесс, кто-то говорит поток :)
Тупиковая ситуация - это ситуация, когда доступных ресурсов меньше, чем того требует другой процесс. Это означает, что когда количество доступных ресурсов становится меньше, чем запрошено пользователем, тогда процесс переходит в состояние ожидания. Иногда ожидание увеличивается еще больше, и тогда нет никакой возможности проверить проблему нехватки ресурсов, тогда эта ситуация известна как тупик. На самом деле, взаимоблокировка - серьезная проблема для нас, и она возникает только в многозадачной операционной системе. Взаимоблокировка не может возникнуть в однозадачной операционной системе, потому что все ресурсы присутствуют только для той задачи, которая в данный момент выполняется ...
Чтобы определить тупик, сначала я бы определил процесс.
Процесс: Как мы знаем, процесс - это не что иное, как исполняемый program.
Ресурс: Для выполнения программного процесса требуются некоторые ресурсы. Категории ресурсов могут включать память, принтеры, процессоры, открытые файлы, ленточные накопители, компакт-диски и т. д.
Тупик: Тупик - это ситуация или условие, когда два или более процесса удерживают некоторые ресурсы и пытаются получить еще несколько ресурсов, и они не могут освободить ресурсы, пока не закончат свое выполнение.
Состояние или ситуация тупиковой ситуации

На приведенной выше диаграмме есть два процесса P1 и p2 и есть два ресурса R1 и R2.
Ресурс R1 выделяется процессу P1, а ресурс R2 выделяется процессу p2. Для завершения выполнения процесса P1 требуется ресурс R2, поэтому запрос P1 для R2, но R2 уже выделен для P2.
Таким же образом процессу P2 для завершения своего выполнения требуется R1, но R1 уже назначен для P1.
оба процесса не могут освободить свой ресурс до тех пор, пока не завершат свое выполнение. Так что оба ждут других ресурсов и будут ждать вечно. Итак, это условие ЗАМОК.
Чтобы возникла взаимоблокировка, должны выполняться четыре условия.
и все эти условия выполнены на диаграмме выше.
Тупик - распространенная проблема в задачах многопроцессорности / мультипрограммирования в ОС. Скажем, есть два процесса P1, P2 и два глобально разделяемых ресурса R1, R2, и в критическом разделе необходимо получить доступ к обоим ресурсам.
Первоначально ОС назначает R1 для обработки P1 и R2 для обработки P2. Поскольку оба процесса работают одновременно, они могут начать выполнение своего кода, но ПРОБЛЕМА возникает, когда процесс попадает в критическую секцию. Таким образом, процесс R1 будет ждать, пока процесс P2 освободит R2, и наоборот ... Так что они будут ждать вечно (СОСТОЯНИЕ ТЕРМОБЛОКИРОВКИ).
Маленькая АНАЛОГИЯ ...
Your Mother(OS),
You(P1),
Your brother(P2),
Apple(R1),
Knife(R2),
critical section(cutting apple with knife).Your mother gives you the apple and the knife to your brother in the beginning.
Both are happy and playing(Executing their codes).
Anyone of you wants to cut the apple(critical section) at some point.
You don't want to give the apple to your brother.
Your brother doesn't want to give the knife to you.
So both of you are going to wait for a long very long time :)
Позвольте мне объяснить реальный (не совсем реальный) пример тупиковой ситуации из криминальных фильмов. Представьте, что преступник держит заложника, а против этого полицейский также держит заложника, который является другом преступника. В этом случае преступник не отпустит заложника, если полицейский не отпустит своего друга. Также коп не собирается отпускать друга преступника, если преступник не освободит заложника. Это бесконечная ненадежная ситуация, потому что обе стороны настаивают на первом шаге друг от друга.
Проще говоря, когда двум потокам нужны два разных ресурса, и каждый из них имеет блокировку ресурса, который нужен другому, это тупиковая ситуация.
Вы встречаетесь с девушкой, и однажды после ссоры обе стороны убиты друг другом и ждут звонка Мне-жаль-и-я-скучал по тебе. В этой ситуации обе стороны хотят общаться друг с другом тогда и только тогда, когда одна из них получает вызов Мне жаль от другой. Поскольку ни один из них не будет начинать связь и ждать в пассивном состоянии, оба будут ждать, пока другой не начнет связь, что приведет к тупиковой ситуации.
Разве потоки не должны принадлежать разным процессам? Могут ли потоки, принадлежащие одному процессу, также вызвать тупик?
@diabolicfreak Не имеет значения, принадлежат потоки к одному процессу или нет.
Другой пример из реальной жизни - четыре машины, одновременно выезжающие на пересечение двух равных дорог в четырех направлениях. Каждый должен уступить дорогу машине с правой стороны, чтобы никто не смог проехать.
Эти примеры из реальной жизни очень наглядны и забавны.
Другой пример из «реальной жизни»: Обедающие философы
Выше некоторые объяснения хороши. Надеюсь, это также может быть полезно: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
В базе данных, когда сеанс (например, ora) хочет, чтобы ресурс находился в другом сеансе (например, данные), но этот сеанс (данные) также хочет ресурс, который удерживается первым сеансом (ora). Также может быть задействовано более двух сессий, но идея останется прежней. Фактически, взаимоблокировки не позволяют некоторым транзакциям продолжать работу. Например: Предположим, ORA-DATA удерживает блокировку A и запрашивает блокировку B И SKU удерживает блокировку B и запрашивает блокировку A.
Спасибо,
Тупиковая ситуация возникает, когда поток ожидает завершения другого потока и наоборот.
Как избежать?
- Избегайте вложенных блокировок
- Избегайте ненужных блокировок
- Использовать соединение потока ()
Как вы это обнаруживаете?
запустите эту команду в cmd:
jcmd $PID Thread.print
ссылка: geeksforgeeks
Классическая и очень простая программа для понимания ситуации Тупик: -
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 был инициализирован и начинает инициализировать класс. В основной поток теперь устанавливает для инициализации значение false, создает и запускает фон поток, метод запуска которого устанавливает значение initialized в значение true, и ожидает завершения фонового потока.
На этот раз класс в настоящее время инициализируется другим потоком. В этих обстоятельствах текущий поток, который является фоновым потоком, ожидает на объекте Class, пока инициализация не будет завершена. К сожалению, нить который выполняет инициализацию, основной поток ожидает фона нить для завершения. Поскольку два потока теперь ждут друг друга, программа ЗАБЛОКИРОВАНО.
Взаимоблокировки возникают не только с блокировками, хотя это наиболее частая причина. В C++ вы можете создать взаимоблокировку с двумя потоками и без блокировок, просто заставив каждый поток вызывать join () в объекте std :: thread для другого.
Использование блокировки для управления доступом к общим ресурсам может привести к взаимоблокировкам, и один только планировщик транзакций не может предотвратить их возникновение.
Например, системы реляционных баз данных используют различные блокировки, чтобы гарантировать свойства транзакции ACID.
Независимо от того, какую систему реляционной базы данных вы используете, блокировки всегда будут выполняться при изменении (например, UPDATE или DELETE) определенной записи таблицы. Без блокировки строки, которая была изменена текущей выполняющейся транзакцией, Atomicity будет скомпрометирован).
Тупиковая ситуация возникает, когда две параллельные транзакции не могут выполняться, потому что каждая из них ждет, пока другая снимет блокировку, как показано на следующей диаграмме.
Поскольку обе транзакции находятся в фазе получения блокировки, ни одна из них не снимает блокировку до получения следующей.
Если вы используете алгоритм управления параллелизмом, основанный на блокировках, всегда существует риск возникновения тупиковой ситуации. Тупиковые ситуации могут возникать в любой среде параллелизма, а не только в системе баз данных.
Например, многопоточная программа может зайти в тупик, если два или более потока ожидают блокировки, которая была установлена ранее, так что ни один поток не может продвинуться вперед. Если это происходит в приложении Java, JVM не может просто заставить поток остановить выполнение и снять блокировки.
Даже если класс Thread предоставляет метод stop, этот метод устарел, начиная с Java 1.1, поскольку он может привести к тому, что объекты останутся в несогласованном состоянии после остановки потока. Вместо этого Java определяет метод interrupt, который действует как подсказка, поскольку поток, который был прерван, может просто игнорировать прерывание и продолжить его выполнение.
По этой причине приложение Java не может выйти из тупиковой ситуации, и разработчик приложения обязан упорядочить запросы на получение блокировки таким образом, чтобы тупиковые ситуации никогда не возникали.
Однако система базы данных не может обеспечить выполнение данного порядка получения блокировок, поскольку невозможно предвидеть, какие еще блокировки потребуется получить в дальнейшем для определенной транзакции. За сохранение порядка блокировок отвечает уровень доступа к данным, и база данных может только помочь в восстановлении после тупиковой ситуации.
Ядро базы данных запускает отдельный процесс, который сканирует текущий граф конфликтов на предмет циклов блокировки-ожидания (которые вызваны взаимоблокировками). Когда цикл обнаружен, ядро базы данных выбирает одну транзакцию и прерывает ее, в результате чего ее блокировки снимаются, так что другая транзакция может выполняться.
В отличие от JVM, транзакция базы данных разработана как элементарная единица работы. Следовательно, откат оставляет базу данных в согласованном состоянии.