В программировании в общем и в Golang в частности существуют проблемные ситуации при конкурентом выполнении задач. Рассмотрим основные из них:
- deadlock - взаимная блокировка
- livelock - динамическая взаимоблокировка
- starvation - голодание
1. Deadlock
Ситуация, когда процесс переходит в состояние ожидания, поскольку другой ожидающий процесс удерживает требуемый ресурс.
Пример из жизни: пара ведет романтический разговор по телефону, и когда наговорились, ни один из них не хочет вешать трубку первым: «Нет, ты повесь трубку первым!» - взаимозаблокировались =)
В Go Deadlock будет, когда две или более горутин занимают ресурсы, которые другие горутины также пытаются занять, создавая бесконечное ожидание.
Программа не сможет завершится, получим:
fatal error: all goroutines are asleep — deadlock!
Условия для появления deadlock. Условия Коффмана
- Взаимное исключение: ресурс занимается одним процессом и не может быть разделен.
- Удержание и ожидание: процесс удерживает ресурс и ожидает другие ресурсы.
- Отсутствие принудительного освобождения: ресурсы можно освободить только добровольно. Процесс, владеющий ресурсом, должен сам освободить его.
- Циклическое ожидание: процессы ожидают ресурсы в циклическом порядке. каждый из процессов ждет доступа к ресурсу, удерживаемому следующим членом последовательности.
Чтобы предотвратить deadlock, необходимо нарушить хотя бы одно из условий, например, избегая циклического ожидания, устанавливая порядок захвата ресурсов.
Для предотвращения deadlock из примера нужно лочить ресурсы в одинаковом порядке в обоих горутинах:
2. Livelock
Ситуация, когда процессы непрерывно изменяют свои состояния в ответ на изменения в других процессах без какой-либо полезной работы.
Пример из жизни: Два человека одновременно пытаются позвонить друг другу, но линия занята. Оба решают повесить трубку и попробовать перезвонить через некоторое время, но снова сталкиваются с занятой линией. Возможно, это та же романтическая парочка из примера с deadlock.
Обнаружить livelock трудно. Программа может реагировать на сигналы, потреблять ресурсы и как то менять состояния, но выйти из цикла и завершить работу не в состоянии.
В функции move каждая горутина (у нас их две) пытается захватить v1 и проверяет, заблокирован ли v2. Если v2 заблокирован, горутина разблокирует v1 и повторяет цикл. Горутины постоянно блокируют и разблокируют ресурсы, но ни одна из них не может продвинуться дальше, поскольку вторая горутина всегда блокирует необходимый ресурс.
3. Starvation
Может быть вызвано deadlock, livelock или процессами, которые занимают ресурсы на длительное время. От блокировок это явление отличается тем, что потоки не заблокированы, а им просто не хватает ресурсов на всех. Поэтому пока одни потоки на себя берут всё время выполнения, другие не могут выполниться.
Пример из жизни: Aэропорт с одной стойкой регистрации и двумя очередями: одна для бизнес-класса, другая для эконома. Бизнес-класс имеет приоритет над экономом и пока всех людей из бизнеса не пропустят - бедняги из эконом-класса будут ждать, а могли бы на поезде ехать.
Функция greedyWorker захватывает sharedResource, делая 10 итераций, в то время как функция starvingWorker ожидает доступа к sharedResource —голодает.