Проблема возникает с синхронизацией.
Время дискретно — события могут «накладываться» по времени. Особенно при работе с неразделяемыми ресурсами.
Печатаем на принтере, передаются страницы. Половина страницы передана — таймер щёлкнул, управление передалось другому процессу, который тоже хочет принтер, — и начал передавать другую страницу. Получили мешанину.
Аналогично — сетевой доступ: половина пакета от одного процесса, половина от другого. Нужна возможность занимать ресурс одним процессом.
4 условия корректной синхронизации
Взаимоисключение (Mutual Exclusion). Одновременно в критической секции относительно ресурса может находиться не более одного процесса.
Прогресс. Не должно быть ситуации: ресурс свободен, есть процесс, готовый его использовать, но реализация алгоритма этого не позволяет («светофорчик»).
Отсутствие голодания. Не должно быть ситуации, когда процесс неограниченно долго ожидает ресурс, передаваемый другим процессам.
Отсутствие тупиков (определение дадим дальше) — кольцевое ожидание ресурсов.
Попытки реализовать взаимоисключение
Переход в однопрограммный режим
У процесса есть механизм, запрещающий прерывания, пока он не выполнит парную операцию. Используется внутри ядра, но в userspace — плохая идея.
Попытка 1: Замок (Lock)
Введём shared-переменную:
shared int lock = 0;
Pi() {
while (lock); // ждём
lock = 1;
{critical_section};
lock = 0;
}
Проблема: в момент между while(lock) и lock = 1 может произойти прерывание, и два процесса окажутся в одной критической секции. Если процессы асинхронны — всё хорошо; если в race condition — это неизбежно.
Попытка 2: Строгое чередование
shared int turn = 0;
Pi() {
while (turn != i);
{critical_section};
turn = 1 - i;
}
Попытка 3: Флаги готовности
shared int ready[2] = {0, 0};
Pi() {
ready[i] = 1;
while (ready[1-i] == 1);
{critical_section};
ready[i] = 0;
}
Один процесс поднял свой флаг, не успев проверить чужой. Другой — то же самое. Оба в тупике — нарушено условие прогресса.
Попытка 4: Алгоритм Петерсона
shared int ready[2] = {0, 0};
shared int turn;
Pi() {
ready[i] = 1;
turn = 1 - i;
while (ready[1-i] == 1 && turn != i);
{critical_section};
ready[i] = 0;
}
Попытка 5: Аппаратная поддержка — Spinlock
shared int lock = 0;
Pi() {
while (test_and_set(&lock)); // атомарно
{critical_section};
lock = 0;
}
Семафоры
Semaphore S;
P(s): while (s == 0)
block(process);
unblock(process);
s = s - 1;
V(s): s = s + 1;
Классическая задача: Producer-Consumer
Два процесса: producer (производитель) и consumer (потребитель).
Semaphore mutex = 1;
Semaphore empty = N;
Semaphore full = 0;
Producer() {
while (true) {
produce_data;
P(empty);
P(mutex);
put_data;
V(mutex);
V(full);
}
}
Consumer() {
while (true) {
P(full);
P(mutex);
get_data;
V(mutex);
V(empty);
consume_data;
}
}