Стек Трайбера — различия между версиями
|  (→Структура стека) | |||
| Строка 2: | Строка 2: | ||
| '''Стек Трайбера''' ''(Treiber Stack)'' —  масштабируеммый ''lock-free'' стек. Считается, что впервые данный алгоритм был опубликовал R. Kent Treiber<ref>[http://domino.research.ibm.com/library/cyberdig.nsf/0/58319a2ed2b1078985257003004617ef?OpenDocument R. Kent Treiber {{---}} Systems Programming: Coping with Parallelism, 1968]</ref>. Алгоритм использует примитив <tex>CAS</tex> ''(compare and set)''. | '''Стек Трайбера''' ''(Treiber Stack)'' —  масштабируеммый ''lock-free'' стек. Считается, что впервые данный алгоритм был опубликовал R. Kent Treiber<ref>[http://domino.research.ibm.com/library/cyberdig.nsf/0/58319a2ed2b1078985257003004617ef?OpenDocument R. Kent Treiber {{---}} Systems Programming: Coping with Parallelism, 1968]</ref>. Алгоритм использует примитив <tex>CAS</tex> ''(compare and set)''. | ||
| == Описание == | == Описание == | ||
| − | ===  | + | === Требования к алгоритму === | 
| − | Основное отличие Treiber stack от однопоточного случая заключается в том, что несколько потоков имеют доступ к данным в стеке одновременно, а значит, могут удалять и добавлять элементы. Чтобы не получилась каша, хотелось бы как-то контролировать процесс взаимодействия потоков.  | + | Основное отличие Treiber stack от однопоточного случая заключается в том, что несколько потоков имеют доступ к данным в стеке одновременно, а значит, могут удалять и добавлять элементы. Чтобы не получилась каша, хотелось бы как-то контролировать процесс взаимодействия потоков. Конечно, это можно было бы сделать просто блокируя каждую операцию, производимую на стеке. Но такая блокировка не есть хорошо, ведь тем самым мы уменьшаем параллелизм, а значит, уменьшаем масштабируемость программы. Уходя от данной стратегии, разрешим потоком работать одновременно со стеком и потребуем от алгоритма условие неблокируемости. | 
| − | |||
| − | |||
| === Lock-freedom алгоритмы и CAS === | === Lock-freedom алгоритмы и CAS === | ||
| − | + | Свойство ''Lock-freedom'' гарантирует прогресс в системе. Для его реализации используется операция <tex>CAS</tex>. | |
| {{Определение   | {{Определение   | ||
| |definition= | |definition= | ||
| Строка 23: | Строка 21: | ||
| == Алгоритм ==   | == Алгоритм ==   | ||
| + | === Идеи === | ||
| + | Переходя от требований к конкретной реализации, введем следующие условия: | ||
| + | #Добавлять новый элемент только если уверены, что добавляемый элемент — единственный с момента начала операции. | ||
| + | #При удалении элемента, перед его возвратом, нужно быть уверенным,что никакой другой поток не добавил новый элемент в стек с начала операции. | ||
| === Структура стека === | === Структура стека === | ||
| Как всегда каждый элемент стека содержит информацию о хранимом значении и указатель на следующий элемент. Также имеем указатель на голову стека <tex>H</tex>, который будем изменять при помощи операции <tex>CAS</tex>. Если при этом голова указывает на <tex>null</tex>, то стек — пуст. | Как всегда каждый элемент стека содержит информацию о хранимом значении и указатель на следующий элемент. Также имеем указатель на голову стека <tex>H</tex>, который будем изменять при помощи операции <tex>CAS</tex>. Если при этом голова указывает на <tex>null</tex>, то стек — пуст. | ||
| Строка 52: | Строка 54: | ||
| <references /> | <references /> | ||
| + | |||
| + | ==См. также== | ||
| + | * [[Алгоритмы_взаимного_исключения]] | ||
| + | |||
| == Источники информации== | == Источники информации== | ||
| * [https://en.wikipedia.org/wiki/Treiber_Stack Wikipedia Treiber {{---}} Stack] | * [https://en.wikipedia.org/wiki/Treiber_Stack Wikipedia Treiber {{---}} Stack] | ||
Версия 11:12, 1 октября 2018
Стек Трайбера (Treiber Stack) — масштабируеммый lock-free стек. Считается, что впервые данный алгоритм был опубликовал R. Kent Treiber[1]. Алгоритм использует примитив (compare and set).
Содержание
Описание
Требования к алгоритму
Основное отличие Treiber stack от однопоточного случая заключается в том, что несколько потоков имеют доступ к данным в стеке одновременно, а значит, могут удалять и добавлять элементы. Чтобы не получилась каша, хотелось бы как-то контролировать процесс взаимодействия потоков. Конечно, это можно было бы сделать просто блокируя каждую операцию, производимую на стеке. Но такая блокировка не есть хорошо, ведь тем самым мы уменьшаем параллелизм, а значит, уменьшаем масштабируемость программы. Уходя от данной стратегии, разрешим потоком работать одновременно со стеком и потребуем от алгоритма условие неблокируемости.
Lock-freedom алгоритмы и CAS
Свойство Lock-freedom гарантирует прогресс в системе. Для его реализации используется операция .
| Определение: | 
| Сравнение с обменом (англ. compare and set, compare and swap, CAS) — атомарная инструкция, сравнивающая значение в памяти с одним из аргументов, и в случае успеха записывающая второй аргумент в память. | 
Ниже представлен псевдокод операции для целочисленных переменных.
fun cas(int* p, int old, int new): bool
    if *p != old 
        return false
    *p = new
    return true
используется для реализации таких примитивов синхронизации, как mutex и semaphore. Это своеобразный базовый "кирпичик" для Lock-freedom алгоритмов, ведь если привел к неудачи, то кто-то другой изменил старое значение. Таким образом, прогресс в системе есть. реализован на уровне атомарных переменных во многих языках программирование, в том числе Java и C.
Алгоритм
Идеи
Переходя от требований к конкретной реализации, введем следующие условия:
- Добавлять новый элемент только если уверены, что добавляемый элемент — единственный с момента начала операции.
- При удалении элемента, перед его возвратом, нужно быть уверенным,что никакой другой поток не добавил новый элемент в стек с начала операции.
Структура стека
Как всегда каждый элемент стека содержит информацию о хранимом значении и указатель на следующий элемент. Также имеем указатель на голову стека , который будем изменять при помощи операции . Если при этом голова указывает на , то стек — пуст.
Удаление элементов
Запомним, на что указывает голова стека (запишем в локальную переменную ). Значение, которое хранит в себе , — то, что необходимо будет вернуть. Попробуем переместить голову стеком ом. Если удалось — вернем . Если нет, то это означает, что с момента начала операции стек был изменен. Поэтому попробуем проделать операцию заново.
Добавление элементов
Запомним, куда указывает голова стека (запишем в локальную переменную ). Создадим новый элемент, который хотим добавить в начало стека. Указатель на следующее значение для него — . Попробуем переместить на новый элемент, при помощи . Если это удалось — добавление прошло успешно. Если нет, то кто-то другой изменил стек, пока мы пытались добавить элемент. Придется начинать сначала.
Псевдокод
fun pop(): Int
    while (true) //Cas loop
      head = H
      if (CAS (&H, head, head.next)) 
        return head.value
fun push(x: Int)
    while (true) //Cas loop
      head = H
      if (head == null)
        throw new EmptyStackException();
      newHead = Node {value: x, next: head}
      if (CAS (&H, head, newHead)) 
        return
