powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Lock-free: Доведём очередь до ума
25 сообщений из 147, страница 1 из 6
Lock-free: Доведём очередь до ума
    #39548674
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Опять не дают мне покоя lock-free алгоритмы, опять немного почитал статьи на Хабре, и опять толком ничего не осилил
В первую очередь меня интересует очередь. Я пытался портировать на Delphi такую реализацию очереди (раздел "Пример использования tagged pointers")

Реализация не взлетела. Может быть я некорректно её переписал, но вот на что я обратил внимание, и что меня не устроило
Смотрите строку D11 ("Читаем значение перед CAS, так как иначе другой dequeue может освободить next")
Т.е. получается, может случиться ситуация, когда в одном потоке произойдёт копирование данных в результат, а другой поток будет финализировать эти данные. Если речь идёт о простых типах - Integer, Pointer и т.д. - то проблем нет. Но если происходит работа с Interface, Variant или строки - ошибка гарантирована.

В общем основы
Очередь представляет собой односвязный список от Head до Tail
Dequeue извлекает элементы из Head, Enqueue добавляет элементы в Tail
НО. Если Dequeue последнего элемента (Head = Tail) - то обнуляются оба указателя
Если Enqueue самого первого элемента (Head = Tail = nil) - то Head тоже инициализируется


В общем я целую неделю опробывал разные подходы с разной результативностью
Основная проблема возникает тогда, когда мы добавляем элементы и ориентируемся на Tail, а Tail тем временем удаляется в другом потоке

В итоге наибольшей стабильности я добился сегодня, когда стал исповедовать правило "кто Tail меняет - тот и задаёт правила"
И мои внутренние тесты показали почти идеальный вариант. Только изредка терялся 1 элемент
Сейчас я ещё больше упростил код, убрав "Tagged pointer" из Next-тов и FTail. Ошибок стало появляться больше. То ли из-за более интенсивной конкуренции, то ли из-за ABA.
Представляю на ваш суд свой код, содержащий ошибки ( есть открытый репозиторий ). Надеюсь, вы свежим взглядом поможете делу

P.S. Да, TSyncPointer - это некий "Tagged pointer", 8 байтовая структура, содержащая указатель и счётчик замен (обычно используется для избежания ABA)

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
  TSyncQueue<T> = class(TSyncObject, ISyncQueue<T>)
  private
    FHead: TSyncPointer;
    FTail: Pointer;

    type
      TNode = TAllocatorNode<Pointer,TNothing,T>;
      PNode = ^TNode;

    function GetEmpty: Boolean; inline;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;

    function Enqueue(const Value: T): Boolean;
    function Dequeue(var Value: T): Boolean;

    property Empty: Boolean read GetEmpty;
  end;


function TSyncQueue<T>.Enqueue(const Value: T): Boolean;
var
  Node, Tail: PNode;
begin
  // новый узел
  Node := TSyncAllocator<Pointer,TNothing,T>.New;
  Node.Value := Value;
  Node.Next := nil;

  // подменяем FTail на узел
  Tail := AtomicExchange(FTail, Node);

  // инициализируем Next предущего узла или сам FHead
  if (Tail = nil) then
  begin
    FHead.Value := Node;
  end else
  begin
    Tail.Next := Node;
  end;
end;

function TSyncQueue<T>.Dequeue(var Value: T): Boolean;
var
  Head: TSyncPointer;
  Tail, Next: Pointer;
begin
  repeat
    Tail := FTail;
    Head := FHead.Copy;

    if (Head.Empty) then
    begin
      // очередь пустая если Tail пустой - выходим
      // может быть задан Tail, а Head ещё не проинициализирован корректным значением
      if (Tail = nil) then
        Exit(False);
    end else
    begin
      // заменяем FHead на Next, при необходимости заменяем FTail
      // Next пустой если ещё не инициализирован (ждём) или последний (обнуляем FTail)
      Next := PNode(Head.Value).Next;
      if (Next = nil) then
      begin
        if (Head.Value = Tail) then
        begin
          // извлекаем единственный элемент - обнуляем FTail
          // и по возможности обнуляем FHead
          if (AtomicCmpExchange(FTail, nil, Tail) = Tail) then
          begin
            FHead.AtomicCmpExchange(nil, Head);
            Break;
          end;
        end else
        begin
          // Next не инициализирован
          // просто ждём
        end;
      end else
      begin
        // Next содержит валидный указатель на следующий узел - кладём его в FHead
        if (FHead.AtomicCmpExchange(Next, Head)) then
          Break;
      end;
    end;
  until (False);

  // результат
  Value := PNode(Head.Value).Value;
  TSyncAllocator<Pointer,TNothing,T>.Dispose(Head.Value);
  Result := True;
end;

...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548677
YuRock
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

Твой код не осилил (не хочу заморачиваться с пониманием новомодных конструкций), но это не важно.
У меня к тебе вопрос.
Ты уверен, что возможно обойтись без блокировок при добавлении/чтении(взятии в обработку элемента) очереди?
Это единственная блокировка, которая нужна, а затраты на нее несравнимо меньше, чем на обработку элемента очереди, зачастую.
Я лично не уверен. Впрочем, могу ошибаться - мало ли что можно понимать под очередью.

Ну просто по логике. Из разных потоков идет добавление в ОДИН список заданий. Ну как может не быть синхронизации этого добавления (и удаления).
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548678
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YuRock,

Уверен :)
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548680
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YuRockИз разных потоков идет добавление в ОДИН список заданий. Ну как может не быть синхронизации этого добавления (и удаления).
AtomicCmpExchange
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548681
Фотография X-Cite
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
YuRock,
На многопроцессорных системах в большинстве случаев LockFree выигрывает у блокировочных алгоритмов. Блокировка это уровень ядра и это затраты на переключение контекста между потоками. В lockfree такие переключения сведены к минимуму т.к. вся суть в атомарных операциях процессора.

Для программ выполняющихся на компьютерах с 1 процессором такие алгоритмы бессмысленны.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548715
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
X-CiteYuRock,
На многопроцессорных системах в большинстве случаев LockFree выигрывает у блокировочных алгоритмов. Блокировка это уровень ядра и это затраты на переключение контекста между потоками. В lockfree такие переключения сведены к минимуму т.к. вся суть в атомарных операциях процессора.
тут очень спорно, та же критическая секция в ядро далеко не сразу не уходит


SOFT FOR YOU, на первый взгляд проблема в том, что не отслеживается версионность выделенного участка памяти, элемент может пролететь несколько раз по цепочке контейнер-аллокатор, и это логически будет уже "другой" элемент.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548720
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

кроме того, надо ещё иногда устранять "гонки"

как вариант (мидийная пауза говорят круче)
Код: pascal
1.
2.
3.
4.
5.
procedure SwitchToNextThread(); inline;
begin
  if (not Winapi.Windows.SwitchToThread()) then
    Winapi.Windows.Sleep(1);
end;



очень существенно ускоряет + проц в пиках не загружается на 100%
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
  TBitLock = record
    BitSet: Integer;
  private
    class procedure Test; static;
  public
    function LockFirstZerro(): Integer; overload; inline;
    function LockFirstZerro(From: Integer): Integer; overload; inline;
    function LockFirstBit(): Integer; overload; inline;
    function LockFirstBit(From: Integer): Integer; overload; inline;
    procedure InverseBit(k: Integer); inline;
    procedure Zerro; inline;
  end;

function TBitLock.LockFirstBit(): Integer;
var
  tmp, a, v: LongWord;
begin
  v := LongWord(BitSet);
  repeat
    tmp := v;
    if tmp = 0 then
      Exit(-1);
    a := (not(tmp - 1)) and tmp;
    v := LongWord(TMyInterlocked.CompareExchange(BitSet, Integer(a xor tmp), Integer(tmp)));
    if (v = tmp) then
      break;

    tmp := v;
    if tmp = 0 then
      Exit(-1);
    a := (not(tmp - 1)) and tmp;
    v := LongWord(TMyInterlocked.CompareExchange(BitSet, Integer(a xor tmp), Integer(tmp)));
    if (v = tmp) then
      break;

    tmp := v;
    if tmp = 0 then
      Exit(-1);
    a := (not(tmp - 1)) and tmp;
    v := LongWord(TMyInterlocked.CompareExchange(BitSet, Integer(a xor tmp), Integer(tmp)));
    if (v = tmp) then
      break;

    SwitchToNextThread();
  until False;
  Result := Trunc(Log2(a));
end;

...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548767
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Версионность есть в FHead. Этого недостаточно?
Приведи пример, где может возникнуть ABA
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548769
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Да, будут бэкофы позже.
Но сейчас вопрос не в них, а в корректностям алгоритма
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548771
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Почему Sleep(1)? Передача неиспользованной части кванта это Sleep(0);
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548782
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUkealon(Ruslan),

Версионность есть в FHead. Этого недостаточно?
Приведи пример, где может возникнуть ABAя на вскидку не скажу, надо всё тестить, а это очень долго

вот это тоже косяк - промежуточное состояние не должно блокировать объект, пользуйся только CAS
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
function TSyncQueue<T>.Enqueue(const Value: T): Boolean;
var
  Node, Tail: PNode;
begin
  // новый узел
  Node := TSyncAllocator<Pointer,TNothing,T>.New;
  Node.Value := Value;
  Node.Next := nil;

  // подмен€ем FTail на узел
  Tail := AtomicExchange(FTail, Node); // вот здесь получишь затык всей очереди - если после этой команды произойдёт переключение потока

  // инициализируем Next предущего узла или сам FHead
  if (Tail = nil) then
  begin
    FHead.Value := Node;
  end else
  begin
    Tail.Next := Node;
  end;
end;




в догонку:
зря ассемблерные вставки наделал, лучше маленько да лучше, TInterlocked из SyncObjs покроет все твои потребности в CAS


методы вроде вот этого
Код: pascal
1.
 property Empty: Boolean

бесполезны
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548819
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Kazantsev Alexeykealon(Ruslan),

Почему Sleep(1)? Передача неиспользованной части кванта это Sleep(0);хз, но так быстрее выходило
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548862
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan),

Сейчас «ассемблер» используется только при замене значения FHead. Возможно ты прав, имеет смысл использовать стандартный двойной CAS. Но он многократно использовался, нареканий по нему нет

Empty. Это простое сравнение Value на nil

Sleep. Это типа backoff стратегии. Принято сначала делать ассемблерную команду pause, потом SwitchToThread, потом Sleep(1). Sleep(0) это относительно долгий простой потока. Быстрее он может быть только на синтетических тестах - за счёт минимизации конкуренции. Стандартные блокировочные алгоритмы, по крайней мере на спинлоке - тоже быстрее на синтетических тестах.

Самое важное. Чтение или запись - сами по себе атомарные операции, кроме того они значительно быстрее. Поначалу я ВРОДЕ БЫ использовал атомики, но очередь по прежнему выдавала ошибки. Если можешь привести пример, где очередь сработает некорректно - милости прошу.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548891
Фотография X-Cite
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
На какой конфигурации железа проходит тестирование. Если несколько процессоров, то могу предположить что не хватает барьеров памяти
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548896
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
X-Cite,

Так лок-фри он без барьеров вроде работает :)
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548914
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUSleep. Это типа backoff стратегии. Принято сначала делать ассемблерную команду pause, потом SwitchToThread, потом Sleep(1). Sleep(0) это относительно долгий простой потока
Вообще-то, Sleep(0) это эквивалент SwitchToThread, с разницей лишь в том, что SwitchToThread отдаёт предпочтение "голодающему" потоку. А любое ненулевое значение в Sleep может усыпить поток очень на долго.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548971
Фотография X-Cite
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOUX-Cite,

Так лок-фри он без барьеров вроде работает :)
Разве?? У каждого процессора свой кеш.. Что будет если 20 процессоров со своими кешами будут непрерывно класть и доставать из очереди элементы. А если это мобильные процессоры, у них слабосвязанный порядок инструкций.
P.s. я не вижу в enqueue while true... Разве в строке
//подменяем FTail на узел - если 20 нитей будут класть не будет ли потери ftail ??? Вроде как вездедолжен быть while true + cas
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39548975
Фотография X-Cite
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ведь суть lockfree это бесконечная попытка чего-либо за конечное число итераций.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549028
YuRock
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
rgreatYuRockИз разных потоков идет добавление в ОДИН список заданий. Ну как может не быть синхронизации этого добавления (и удаления).
AtomicCmpExchange
А работает эта функция (в винде) уж не через InterlockedExchange ли?
А то о ней Майкросовт пишет так: function prevents more than one thread from using the same variable simultaneously.
Перевод: функция предотвращает одновременное использование одной и той же переменной несколькими потоками .

Какими средствами это достигается - объектами ядра или спец. командами процессора (или ф-циями, которые их вызывают) - я вообще-то не уточнял. Я о логике говорил (ты это слово не процитировал). А смысл один - синхронный доступ к списку заданий.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549170
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
авторThis function is implemented using a compiler intrinsic where possible. For more information, see the WinBase.h header file and _InterlockedExchange.

This function generates a full memory barrier (or fence) to ensure that memory operations are completed in order.
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549212
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ребят, давайте ближе к теме
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549257
Aleksandr Sharahov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

а почему FTail не защищен от ABA?

Допустим, поток надолго засыпает в Dequeue прямо перед оператором
if (AtomicCmpExchange(FTail, nil, Tail) = Tail) then

За то время, пока он спал, очередь переходит в другое состояние,
в ней уже 100500 элементов, причем последний сидит по тому же адресу.

Этот if успешно выполнится, но из очереди будет взят последний,
FTail обнулится и последующие Enqueue будут добавлять
в *пустую* с их точки зрения очередь.

Усе пропало, шеф ))
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549314
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Aleksandr Sharahov,

Привет :)
Да, наверное ты прав
Скажи, а Node.Next должен быть защищён от ABA или нет?

Ещё момент. В Enqueue я подменяю FTail.
Его обязательно делать с инкрементом счётчика или не обязательно?
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549315
SOFT FOR YOU
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Если говорить о той реализации, какая есть - то конкуренции за Next вообще нет, он просто инициализируется и читается
Не таит ли такое допущение каких-то неприятных подводных камней?

Но больше всего у меня вопросов по Head
В Enqueue я инициализирую FHead-указатель. Данный код переписывает значение, а счётчик оставляет прежним. Как ты думаешь, где-то это может сыграть неприятную роль?

И подозреваю я, что ошибка кроется как раз здесь. Когда в Enqueue голова просто инициализируется, а в Dequeue меняется атомиком
...
Рейтинг: 0 / 0
Lock-free: Доведём очередь до ума
    #39549461
Aleksandr Sharahov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
SOFT FOR YOU,

море литературы на эту тему, зачем изобретать грабли, на которые уже кто-то наступил.

На мой взгляд, чтобы не тратить время впустую имеет смысл соорудить конечный автомат
и погонять его как следует, а уже потом пробовать строго доказать корректность.
...
Рейтинг: 0 / 0
25 сообщений из 147, страница 1 из 6
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Lock-free: Доведём очередь до ума
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]