Алгоритмы взаимного исключения — различия между версиями
(→Алгоритм Петерсона) |
(→Алгоритмы взаимного исключения) |
||
| Строка 30: | Строка 30: | ||
==Алгоритмы взаимного исключения== | ==Алгоритмы взаимного исключения== | ||
| − | ===Алгоритм Петерсона=== | + | |
| + | ===Алгоритм Петерсона для <tex>2</tex> потоков=== | ||
| + | Простейший алгоритм параллельного программирования для взаимного исключения потоков исполнения кода, разработанный Гарри Петерсоном в <tex>1981</tex> г. Хотя изначально был сформулирован для 2-поточного случая, алгоритм может быть обобщён для произвольного количества потоков. Гарантирует взаимное исключение, отсутствие взаимной блокировки и отсутствие голодания. | ||
| + | |||
| + | Принцип работы: перед тем как начать исполнение критической секции кода, поток должен вызвать процедуру <tex>lock()</tex> со своим номером в качестве параметра. Она должна организовать ожидание потоком своей очереди входа в критическую секцию. После исполнения критической секции и выхода из неё поток вызывает другую процедуру <tex>unlock()</tex>, после чего уже другие потоки смогут войти в критическую область. Посмотрим, как реализуется этот общий принцип алгоритмом Петерсона для двух потоков. | ||
| + | |||
| + | '''threadlocal int''' id <font color=green>// 0 or 1</font> | ||
| + | '''shared int''' want[2] | ||
| + | '''shared int''' victim | ||
| + | '''def''' lock: | ||
| + | want[id] = true | ||
| + | victim = id | ||
| + | '''while''' want[1-id] '''and''' | ||
| + | victim == id: | ||
| + | '''pass''' | ||
| + | |||
| + | '''def''' unlock: | ||
| + | want[id] = false | ||
| + | |||
| + | ===Корректность алгоритма=== | ||
| + | =====Взаимное исключение===== | ||
| + | Потоки <tex>0</tex> и <tex>1</tex> никогда не могут попасть в критическую секцию в один момент времени: если <tex>0</tex> вошёл в секцию, он установил <tex>want[0]</tex> в <tex>true</tex>. Тогда либо <tex>want[1] == false</tex> (тогда поток <tex>1</tex> не в критической секции), либо <tex>waiting == 1</tex> (тогда поток <tex>1</tex> пытается войти в критическую секцию и крутится в цикле), либо поток <tex>1</tex> пытается войти в критическую секцию после установки <tex>want[1] == true</tex>, но до установки <tex>waiting</tex> и цикла. Таким образом, если оба процесса находятся в критической секции, должно быть <tex>want[0] && want[1] && waiting == 0 && waiting == 1</tex>, но такого не может быть одновременно и мы пришли к противоречию. | ||
| + | =====Свобода от взаимной блокировки===== | ||
| + | Для того, чтобы оба процесса находились в ожидании, необходимы противоположные значения переменной <tex>waiting</tex>, что невозможно. | ||
| + | =====Свобода от голодания===== | ||
| + | Возможна ситуация, когда один процесс будет несколько раз подряд захватывать себе критическую секцию, а другой, изъявивший желание попасть туда, будет ждать. В алгоритме Петерсона процесс не будет ждать дольше, чем один вход в критическую секцию: после выполнения <tex>unlock()</tex> и повторного захода в <tex>lock()</tex> процесс установит себя как ждущего и попадёт в цикл, который не завершится, пока не отработает другой процесс. | ||
| + | |||
| + | ===Алгоритм Петерсона для <tex>N</tex> потоков=== | ||
Перед тем как начать исполнение критической секции кода, поток должен вызвать процедуру <tex>lock()</tex> со своим номером в качестве параметра. Она должна организовать ожидание потоком своей очереди входа в критическую секцию. После исполнения критической секции и выхода из неё поток вызывает другую процедуру <tex>unlock()</tex>, после чего уже другие потоки смогут войти в критическую область. Посмотрим, как реализуется этот общий принцип алгоритмом Петерсона. | Перед тем как начать исполнение критической секции кода, поток должен вызвать процедуру <tex>lock()</tex> со своим номером в качестве параметра. Она должна организовать ожидание потоком своей очереди входа в критическую секцию. После исполнения критической секции и выхода из неё поток вызывает другую процедуру <tex>unlock()</tex>, после чего уже другие потоки смогут войти в критическую область. Посмотрим, как реализуется этот общий принцип алгоритмом Петерсона. | ||
Версия 11:19, 25 сентября 2018
Содержание
- 1 Определения
- 2 Проблема
- 3 Алгоритмы взаимного исключения
- 4 См. также
- 5 Источники информации
Определения
| Определение: |
| Взаимное исключение (англ. mutual exclusion) — свойство построения параллельных программ, которое используется в целях предотвращения состояния гонки (англ. race condition); Оно требует, чтобы один поток исполнения никогда не входил в критическую секцию одновременно с тем, как другой параллельный поток выполнения вошел в свою критическую секцию. |
То есть критические секции не могут выполняться параллельно: . Это значит, что выполнение критических секций будет линеаризуемо. Это требование корректности протокола взаимной блокировки.
| Определение: |
| Критическая секция (англ. critical section) — участок исполняемого кода программы, в котором производится доступ к общему ресурсу (данным или устройству), который не должен быть одновременно использован более чем одним потоком исполнения. |
Проблема
Проблема, с которой связаны взаимные исключения, является проблемой совместного использования ресурсов: как можно управлять доступом нескольких процессов к общему ресурсу, когда каждый процесс нуждается в исключительном контроле над этим ресурсом при выполнении своей работы? Решение — делать доступным общий ресурс только тогда, когда процесс находится в определенном сегменте кода, называемом критической секцией. И контролировать доступ к общему ресурсу, контролируя каждое взаимное выполнение той части программы, в которой будет использоваться ресурс.
Успешное решение этой проблемы должно иметь по крайней мере три свойства:
- условие корректности
- Взаимное исключение: только один поток может быть в критической секции.
- условия прогресса
- Отсутствие взаимоблокировок (англ. deadlocks): если несколько потоков пытаются войти в критическую секцию, то хотя бы один из них должен войти в критическую секцию за конечное время.
- Отсутствие голодания (англ. starvation-freedom): если какой-то поток пытается войти в критическую секцию, то он войдет в критическую секцию за конечное время.
- Non-Critical Section
- Операция находится вне критической секции; этот процесс не использует или не запрашивает общий ресурс.
- Trying
- Процесс пытается войти в критический раздел.
- Critical Section
- В этом разделе разрешен доступ к общему ресурсу.
- Exit
- Процесс выходит из критического раздела и делает доступный общий ресурс другим процессам.
Если процесс хочет войти в критический раздел, он должен сначала выполнить раздел и подождать, пока он не получит доступ к критическому разделу. После того, как процесс выполнил свой критический раздел и завершился с общими ресурсами, ему необходимо выполнить раздел выхода, чтобы освободить их для использования другими процессами. Затем процесс возвращается в некритический раздел.
Алгоритмы взаимного исключения
Алгоритм Петерсона для потоков
Простейший алгоритм параллельного программирования для взаимного исключения потоков исполнения кода, разработанный Гарри Петерсоном в г. Хотя изначально был сформулирован для 2-поточного случая, алгоритм может быть обобщён для произвольного количества потоков. Гарантирует взаимное исключение, отсутствие взаимной блокировки и отсутствие голодания.
Принцип работы: перед тем как начать исполнение критической секции кода, поток должен вызвать процедуру со своим номером в качестве параметра. Она должна организовать ожидание потоком своей очереди входа в критическую секцию. После исполнения критической секции и выхода из неё поток вызывает другую процедуру , после чего уже другие потоки смогут войти в критическую область. Посмотрим, как реализуется этот общий принцип алгоритмом Петерсона для двух потоков.
threadlocal int id // 0 or 1
shared int want[2]
shared int victim
def lock:
want[id] = true
victim = id
while want[1-id] and
victim == id:
pass
def unlock:
want[id] = false
Корректность алгоритма
Взаимное исключение
Потоки и никогда не могут попасть в критическую секцию в один момент времени: если вошёл в секцию, он установил в . Тогда либо (тогда поток не в критической секции), либо (тогда поток пытается войти в критическую секцию и крутится в цикле), либо поток пытается войти в критическую секцию после установки , но до установки и цикла. Таким образом, если оба процесса находятся в критической секции, должно быть , но такого не может быть одновременно и мы пришли к противоречию.
Свобода от взаимной блокировки
Для того, чтобы оба процесса находились в ожидании, необходимы противоположные значения переменной , что невозможно.
Свобода от голодания
Возможна ситуация, когда один процесс будет несколько раз подряд захватывать себе критическую секцию, а другой, изъявивший желание попасть туда, будет ждать. В алгоритме Петерсона процесс не будет ждать дольше, чем один вход в критическую секцию: после выполнения и повторного захода в процесс установит себя как ждущего и попадёт в цикл, который не завершится, пока не отработает другой процесс.
Алгоритм Петерсона для потоков
Перед тем как начать исполнение критической секции кода, поток должен вызвать процедуру со своим номером в качестве параметра. Она должна организовать ожидание потоком своей очереди входа в критическую секцию. После исполнения критической секции и выхода из неё поток вызывает другую процедуру , после чего уже другие потоки смогут войти в критическую область. Посмотрим, как реализуется этот общий принцип алгоритмом Петерсона.
threadlocal int id // 0 to N-1
shared int level[N]
shared int victim[N]
def lock:
for j = 1..N-1:
level[id] = j
victim[j] = id
while exist k: k != id and
level[k] >= j and
victim[j] == id:
pass
def unlock:
level[id] = 0
Алгоритм Лампорта (вариант )
threadlocal int id // 0 to N-1
shared boolean want[N] init false
shared int label[N] init 0
def lock:
want[id] = true
label[id] = max(label) + 1
while exists k: k != id and
want[k] and
(label[k], k) < (label[id], id) :
pass
def unlock:
want[id] = false
Алгоритм Лампорта (вариант )
threadlocal int id // 0 to N-1
shared boolean want[N] init false
shared int label[N] init 0
def lock:
choosing[id] = true
label[id] = max(label!=inf) + 1
choosing[id] = false
while exists k: k != id and
(choosing[k] or
(label[k], k) < (label[id], id)) :
pass
def unlock:
label[id] = inf