Очередь Майкла и Скотта — различия между версиями
(→Идея реализации) |
|||
| Строка 42: | Строка 42: | ||
T.next = newTail <font color=green>//Добавление новой вершины в очередь</font> | T.next = newTail <font color=green>//Добавление новой вершины в очередь</font> | ||
T = T.next <font color=green>//Изменение хвоста списка</font> | T = T.next <font color=green>//Изменение хвоста списка</font> | ||
| + | |||
| + | == Многопоточная реализация == | ||
| + | |||
| + | Будем при всех изменениях указателей на вершины списка использовать <tex>CAS</tex> (то есть при изменении <tex>T</tex>, <tex>H</tex>, и <tex>T.next</tex>) | ||
| + | |||
| + | === Удаление элемента === | ||
| + | |||
| + | Для удаления элемента необходимо переместить указатель <tex>H</tex> на следующую в списке вершину. | ||
| + | |||
| + | '''def''' pop(): '''Int''' | ||
| + | while (true): <font color=green>//Поток пытается в CAS - цикле поменять указатель на H, пока не получится</font> | ||
| + | head = H.get() | ||
| + | if (head.next == null): | ||
| + | '''throw''' new EmptyException() | ||
| + | newHead = head.next.get() | ||
| + | if (CAS(H, head, nextHead)): | ||
| + | return newHead.data | ||
| + | |||
| + | === Добавление элемента === | ||
| + | |||
| + | Создадим новый узел списка, и добавим его в конец очереди. | ||
| + | |||
| + | '''def''' push(x: '''Int'''): | ||
| + | newTail = '''new''' Node(x, '''new''' AtomicReference<Node>(null)) | ||
| + | while (true): <font color=green>//Поток пытается в CAS - цикле поменять T.next, пока не получится</font> | ||
| + | tail = T.get() | ||
| + | curTail = tail.next | ||
| + | if (CAS(curTail, null, newTail)): <font color=green>//Поток пытается добавить элемент в конец очереди</font> | ||
| + | break | ||
| + | while (true): <font color=green>//Поток пытается в CAS - цикле поменять указатель на T, пока не получится</font> | ||
| + | tail = T.get() | ||
| + | nextTail = tail.next.get() | ||
| + | if (CAS(T, tail, nextTail)): | ||
| + | break | ||
| + | |||
| + | При этом мы сталкиваемся со следующей проблемой | ||
| + | |||
| + | === Описание проблемы === | ||
==Примечания== | ==Примечания== | ||
Версия 18:01, 1 октября 2018
Очередь Майкла и Скотта (Michael-Scott Queue) - алгоритм построения lock-free очереди. Впервые был предложен Maged M. Michael и Michael L. Scot в статье [1].
Содержание
Структура очереди
Очередь моделируется с помощью односвязного списка. Каждый элемент списка () содержит ссылку на хранимые в нём данные и указатель на следующий элемент списка (который можно менять атомарно).
case class Node(val data: Int, val next: AtomicReference<Node>)
Если узел является последним в списке, то его указывает на .
Сама очередь состоит из двух указателей: на голову и на хвост , которые можно менять атомарно. Удаление из очереди происходит со стороны головы, добавление - со стороны хвоста.
Узел списка, на который указывает , является фиктивным (dummy). Данные, хранимые в этом узле, не имеют значения. Изначально очередь состоит из одного dummy-элемента, на который указывают и .
class Queue
dummy = new Node(null, new AtomicReference<Node>(null))
head = new AtomicReference<Node>(dummy)
tail = new AtomicReference<Node>(dummy)
// TODO; картинка
Будем поддерживать следующий инвариант: в нашей очереди указывает на узел, находящийся не правее узла, на который указывает
Идея реализации
Удаление элемента
Для удаления элемента необходимо переместить указатель на следующую в списке вершину.
def pop(): Int
if (H.next == null):
throw new EmptyException()
H = H.next
return H.data //H - новый фиктивный элемент
Добавление элемента
Создадим новый узел списка, и добавим его в конец очереди.
def push(x: Int):
newTail = new Node(x, new AtomicReference<Node>(null))
T.next = newTail //Добавление новой вершины в очередь
T = T.next //Изменение хвоста списка
Многопоточная реализация
Будем при всех изменениях указателей на вершины списка использовать (то есть при изменении , , и )
Удаление элемента
Для удаления элемента необходимо переместить указатель на следующую в списке вершину.
def pop(): Int
while (true): //Поток пытается в CAS - цикле поменять указатель на H, пока не получится
head = H.get()
if (head.next == null):
throw new EmptyException()
newHead = head.next.get()
if (CAS(H, head, nextHead)):
return newHead.data
Добавление элемента
Создадим новый узел списка, и добавим его в конец очереди.
def push(x: Int):
newTail = new Node(x, new AtomicReference<Node>(null))
while (true): //Поток пытается в CAS - цикле поменять T.next, пока не получится
tail = T.get()
curTail = tail.next
if (CAS(curTail, null, newTail)): //Поток пытается добавить элемент в конец очереди
break
while (true): //Поток пытается в CAS - цикле поменять указатель на T, пока не получится
tail = T.get()
nextTail = tail.next.get()
if (CAS(T, tail, nextTail)):
break
При этом мы сталкиваемся со следующей проблемой
Описание проблемы
Примечания
Источники информации
- Maurice Herliny & Nir Shavit - The Art of Multiprocessor programming, стр 230