Современные процессоры имеют на борту по нескольку вычислительных ядер, позволяющих запускать задачи на них параллельно. И, казалось бы, вот оно — счастье: бей большие задачи на куски, запускай эти куски параллельно на разных ядрах и радуйся.
Но не все так просто. Для того чтобы одновременный доступ к общим данным выполнялся корректно, современные системы используют разные примитивы синхронизации. В основе одних лежат блокировки (locks), в основе других — операции типа сравнение-с-обменом (compare-and-swap). Однако и у тех и у других есть свои слабые места. О них мы и поговорим.
Из доклада вы узнаете, чем блокирующие алгоритмы отличаются от неблокирующих, и какими достоинствами и недостатками обладает каждый из этих классов. Кроме того, будут показаны различные подводные камни тех и других решений: Deadlock, Livelock, Starvation, Mutable vs Immutable hype.
5. 5
Чего не будет в презентации
• Определений из учебника
- Больше интересует сама концепция
• Сравнения производительности
• Советов, как правильно писать код
• Холиваров*
• Серебряных пуль, волшебных фреймворков и т.п.
* Нет, ну если кто-то очень захочет, то можно, конечно…
6. 6
А что будет-то?
• Пара простых многопоточных примеров
• Куча связанных с ними проблем
• Варианты решений
- Которые, разумеется, не работают
- Ну некоторые работают
- Иногда
- Наверное…
10. 10
public void transfer (
Account from, Account to, int amount) {
if (from.get() < amount) {
throw new RuntimeException();
} else {
from.set(from.get() - amount);
to.set(to.get() + amount);
}
}
11. 11
public void transfer (
Account from, Account to, int amount) {
if (from.get() < amount) {
throw new RuntimeException();
} else {
from.set(from.get() - amount);
to.set(to.get() + amount);
}
}
Какие тут есть проблемы в многопоточной среде?
12. 12
Кто такой lock
• lock.lock
- Вход в критическую секцию
- свободно — взять
- занято — ждать, пока освободится
• lock.unlock
- Выход из критической секции
- освободить
13. 13
public void transfer (
Account from, Account to, int amount) {
lock(bank);
if (from.get() < amount) {
throw new RuntimeException();
} else {
from.set(from.get() - amount);
to.set(to.get() + amount);
}
unlock(bank);
}
14. 14
public void transfer (
Account from, Account to, int amount) {
lock(bank);
if (from.get() < amount) {
throw new RuntimeException();
} else {
from.set(from.get() - amount);
to.set(to.get() + amount);
}
unlock(bank);
}
А тут в чем проблема?
15. 15
public void transfer (
Account from, Account to, int amount) {
lock(from); lock(to);
if (from.get() < amount) {
throw new RuntimeException();
} else {
from.set(from.get() - amount);
to.set(to.get() + amount);
}
unlock(to); unlock(from);
}
А тут в чем проблема?
18. 18
Проблемы с обедающими философами
Пусть каждый философ действует по
некоторому алгоритму
• Могут ли все философы умереть с голоду?
• Можно ли составить такой алгоритм,
чтобы все философы гарантированно не
умерли с голоду?
19. 19
Параметры задачи
• Количество философов
• Есть ли возможность положить вилку, не пожрамши
• Сколько времени философ ест
- Фиксированное или случайная величина
• Сколько времени философ размышляет
• Какие ещё инструменты/элементы есть в системе?
• Что ещё?
28. 28
Взаимоблокировка (Deadlock)
Взаимоблокировка – такое состояние системы, при котором
два или более процессов не могут продолжать своё
выполнение из-за отсутствия необходимых для этого
ресурсов.
Каждый ждёт другого, поэтому никто не может продолжить
29. 29
Выгружаемые и невыгружаемые ресурсы
• Выгружаемые ресурсы — ресурсы, которые могут быть
безболезненно отобраны у процесса, который ими обладает
• Невыгружаемые ресурсы — ресурсы, которые нельзя
отобрать у процесса, не вызвав при этом сбой в вычислениях
• Мы будем говорить, в основном, о невыгружаемых ресурсах
30. 30
Операции над невыгружаемыми ресурсами
• Запрос ресурса
- Берём ресурс
- или ждём (встаём в «очередь» ожидания)
• Использование ресурса
• Освобождение ресурса
43. 43
Стратегии борьбы с блокировками
• Игнорирование проблемы
• Обнаружение и восстановление
• Динамическое уклонение
• Предотвращение за счёт подавления любого из
четырёх условий Коффмана
49. 49
Обнаружение взаимоблокировок и
восстановление работоспособности
• Шаги
- Позволить блокировке произойти
- Пытаться обнаружить момент возникновения
- Попробовать восстановить работоспособность
В нашем примере можно просто перезапускать
философов
50. 50
Выход из взаимоблокировки
• Приоритетный захват ресурсов
- Приоритезировать (все) процессы
- Отобрать ресурс у менее приоритетного процесса
• Откат (см. след. слайд)
• Уничтожение и перезапуск процессов
52. 52
Выход из взаимоблокировки — Откат
• Периодически создаются контрольные точки
• При обнаружении блокировки происходит откат
• При откате часть работы (которая была выполнена после
прохождения последней контрольной точки) теряется
• Пример: MS SQL Server
- При взаимной блокировке двумя транзакциями друг
друга выбирается одна из них («жертва»), которую
сервер откатывает.
56. 56
Атака условия взаимного исключения
• Возможна редко — часто программа становится
некорректной
• Идея — убирать ненужные блокировки
- Делать нужно осторожно, чтобы функциональность
не страдала
- Заменять на другие механизмы типа CAS
57. 57
Атака условия ожидания и удержания
• Запрашивать ВСЕ необходимые ресурсы не в
процессе работы, а до начала работы
- Но не всегда ресурсы известны заранее
• Вначале временно высвободить все удерживаемые
ресурсы
- атомарно?
59. 59
Атака условия циклического ожидания
• Нумерация ресурсов!
- Захватывать ресурсы только в порядке возрастания номеров
1
2 3
4
5
1
2
3
4
5
C
S
D
R
66. 66
Голодание (Starvation)
• Голодание — ситуация, в которой поток, от которого
ожидается прогресс, (практически) стоит на месте.
• Заблуждение
- Голодание может осуществиться, только если потоки
с более высоким приоритетом постоянно берут
ресурсы, которые нужны низкоприоритетному