C-код незаблокированной очереди

Как я могу реализовать этот псевдокод очереди без блокировки в C?

ENQUEUE(x)
    q ← new record
    q^.value ← x
    q^.next ← NULL
    repeat
        p ← tail
        succ ← COMPARE&SWAP(p^.next, NULL, q)
        if succ ≠ TRUE
            COMPARE&SWAP(tail, p, p^.next)
    until succ = TRUE
    COMPARE&SWAP(tail,p,q)
end

DEQUEUE()
    repeat
        p ← head
        if p^.next = NULL
            error queue empty
    until COMPARE&SWAP(head, p, p^.next)
    return p^.next^.value
end

Как будет использоваться встроенные функции для атомарного доступа к памяти

__sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

в настоящее время у меня есть

typedef struct queueelem {
    queuedata_t data;
    struct queueelem *next;
} queueelem_t;

typedef struct queue {
    int capacity;
    int size;
    queueelem_t *head;
    queueelem_t *tail;
} queue_t;

queue_t *
queue_init(int capacity)
{
    queue_t *q = (queue_t *) malloc(sizeof(queue_t));
    q->head = q->tail = NULL;
    q->size = 0;
    q->capacity = capacity;
    return q;
}

person edgarmtze    schedule 23.05.2011    source источник
comment
Единственное удобство использования глобальных переменных заключается в том, что это позволяет вам быть ленивым и избегать ввода текста. Поскольку вы занимаетесь параллельным программированием, вам должно быть стыдно, что вы вообще задали этот вопрос. :-)   -  person R.. GitHub STOP HELPING ICE    schedule 23.05.2011
comment
Было бы полезно, если бы вы сначала реализовали полный не потокобезопасный вариант, а позже попытались сделать его потокобезопасным. Что касается глобального, читайте комментарий выше.   -  person hackworks    schedule 23.05.2011
comment
Чем это отличается (например, не является дубликатом) от последнего заданного вами вопроса?   -  person abelenky    schedule 23.05.2011
comment
Обратите внимание, что имена, оканчивающиеся на _t, зарезервированы в некоторых стандартах (POSIX, не уверен насчет фактического стандарта) и не должны использоваться в вашем коде. Вы должны использовать другое соглашение об именах.   -  person Chris Lutz    schedule 23.05.2011
comment
возможный дубликат свободной от блокировки очереди   -  person Jens Gustedt    schedule 23.05.2011
comment
некоторые правила, по которым нужно жить. 1) Всегда добавлять/удалять из HEAD списка. Быстрее! Проще! Один CAS будет добавлять/удалять элементы из списка, и вы устраните массу условий гонки. Если вам нужно выполнить более одного CAS, вам придется иметь дело с условиями гонки. Изменение следующего указателя на хвостовом элементе, а затем одновременное изменение головы или хвоста невозможно с помощью одного CAS. изменение главного указателя списка! 2) беспокойтесь об ABA только в том случае, если вы удаляете элемент и возвращаете его обратно. Если вы добавите новые точки только в список, у вас не будет ABA.   -  person johnnycrash    schedule 16.06.2011


Ответы (5)


https://www.liblfds.org/

Общественное достояние, без лицензии, переносимая реализация алгоритмов без блокировок на C.

Готовится из коробки для Windows и Linux.

Использует GCC в Linux, поэтому использует встроенные функции (ну, кроме 128-битного CAS, встроенной нет - для этого используется встроенная сборка).

Содержит очередь M&S. Взгляните на исходный код и посмотрите, как это делается.

person Community    schedule 26.05.2011
comment
Похоже сайт не работает - person Schneems; 13.06.2017

Если ваша цель — производственный код, просто не делайте этого; использовать замки.

В вашем предыдущем вопросе вы получили достаточно информации, объясняющей почему. Правильная безблокировочная реализация даже простых структур данных, таких как очередь и стек, в отсутствие сборщика мусора сложна и изощренна из-за известная проблема ABA. К сожалению, некоторые исследовательские работы по тем или иным причинам не учитывают АВА; ваш псевдокод, похоже, взят из одной из таких статей. Если вы переведете его на C и будете использовать память, выделенную кучей для узлов, это вызовет недетерминированные ошибки при использовании в реальном коде.

Если вы делаете это, чтобы набраться опыта, то не ждите, что ТАК ребята решат это за вас. Вы должны прочитать все приведенные материалы и многое другое, убедиться, что вы действительно понимаете все нюансы lock-free алгоритмов, таких как ABA, изучить различные методы, предназначенные для решения проблемы, изучить существующие реализации lock-free и т. д.

Наконец, небольшое руководство по переводу данного псевдокода на C:

q^.value ← x означает, что q_elem->data = x;
repeat ... until COMPARE&SWAP(head, p, p^.next) эквивалентно do {...} while (!__sync_bool_compare_and_swap(q_obj->head, q_elem, q_elem->next);

где q_obj — экземпляр типа queue_t (т. е. очередь), а q_elem — экземпляр типа queueelem_t (т. е. узел очереди).

person Alexey Kukanov    schedule 23.05.2011
comment
Итак, что мне делать, если мне нужно больше производительности в продакшене? ИМХО Блокировки излишни почти для всех условий гонки. - person johnnycrash; 16.06.2011
comment
Если бы я мог получить очередь без блокировки, я бы предпочел ее, особенно в производственном коде. - person Clearer; 05.07.2017

Хотя это и не совсем C, ознакомьтесь с предлагаемой библиотекой Boost.Lockfree. Внутренности довольно легко понять, и их можно портировать на C, или, наоборот, вы можете обернуть Boost.Lockfree в C API и использовать его.

Точно так же на Boostcon 2010 было много дискуссий о программировании без блокировки и STM, на которые стоит обратить внимание, если вы Интересуетесь этой темой. Я не могу найти ссылку на видео, но доклады Intel, IBM и AMD стоило посмотреть, поскольку они имеют дело с STM на уровне процессора.

person Sean    schedule 23.05.2011

Похоже, что то, что вам нужно, называется блокировкой очереди MCS (хотя это обманчивое название, на самом деле это не блокировка, а просто не освобождение от ожидания), и здесь есть хороший псевдокод: http://www.cs.rochester.edu/research/synchronization/pseudocode/ss.html#mcs< /а>

person mattst88    schedule 23.05.2011
comment
Это не обманчивое название, потому что он реализует блокировку, то есть алгоритм взаимного исключения. - person Alexey Kukanov; 23.05.2011

Я использую C для написания минимизации реализации очереди без блокировки.

lfq.

Его поддерживают многие производители, многие потребители.

person Darkautism Nong    schedule 22.02.2017
comment
Вам не нужен asm volatile( "lfence" ) для грузового барьера. На x86 достаточно asm("" ::: "memory"). Кажется, вы путаете модель памяти времени компиляции С++ с моделью памяти времени выполнения x86. См. Когда следует использовать _mm_sfence _mm_lfence и _mm_mfence. Вам действительно не нужны какие-либо ручные ассемблерные материалы или volatile, просто используйте C11 stdatomic. - person Peter Cordes; 04.09.2018
comment
Также ваш писатель и читатель спорят друг с другом за ctx->count. В lfq_enqueue разве p->next = insert_node; не должно быть внутри цикла CAS или что-то в этом роде? Ваш текущий цикл CAS в этой функции может быть оптимизирован до безусловного ctx->tail = insert_node, поэтому, вероятно, он не делает то, что вы намеревались. mb() также не нужен; в этот момент вы изменили только частные объекты. - person Peter Cordes; 04.09.2018
comment
В любом случае, написание собственной незаблокированной очереди — хороший способ научиться чему-то новому, но ваша пока не выглядит готовой для использования другими людьми. (И обычно вы должны использовать циклический буфер фиксированного размера, чтобы избежать выделения/освобождения при каждой операции с очередью. В некоторых реализациях существует один глобальный пул свободных мест, поэтому calloc/free конкурируют друг с другом за доступ к одной и той же структуре данных! ) - person Peter Cordes; 04.09.2018
comment
Как использовать ctx-›tail = insert_node для предотвращения проблем с ABA? Не могли бы вы показать мне какой-нибудь пример кода? - person Darkautism Nong; 05.09.2018
comment
mb() - это строка 120? Если вы закомментируете строку 120, писатель получит insert_node до того, как вы назначили insert_node в ctx-›tail. Я не могу пройти 1-часовой тест, если закомментирую строку 120. Я рад, что кто-то может помочь мне улучшить знания о структуре без блокировки. Но я не могу удалить этот барьер памяти без сбоя. - person Darkautism Nong; 05.09.2018
comment
Это мой тест скорости: версия lmb() реальная 0m12.473s пользователь 1m24.386s sys 0m5.008s версия mb() реальная 0m15.719s пользователь 1m25.282s sys 0m3.778s версия mb всегда тратит больше времени. Не могли бы вы сказать мне причину, по которой вы не используете lfence? - person Darkautism Nong; 05.09.2018
comment
Вам не нужно lfence, потому что вы не выполняете movntdqa загрузки из видеопамяти (или другой области памяти WC). Это в основном единственный вариант использования порядка использования памяти. Я уже связал Когда я должен использовать _mm_sfence _mm_lfence и _mm_mfence, что объясняет это и что вам делать нужно для получение нагрузки в C по сравнению с x86 asm. Я все равно не понимаю, почему вы используете грузовой барьер в inHP(). Я мог бы увидеть, может быть, один раз за пределами цикла, чтобы сделать его приобретением, если вы катите свои собственные атомарные с volatile. - person Peter Cordes; 05.09.2018
comment
mfence медленнее, чем lfence, поэтому, конечно, медленнее с mb(). Но иногда вам действительно нужно mfence для корректности (или лучше хранить xchg). Если ваш mb() избегает ошибок, возможно, это связано с тем, что эта функция встраивается в вызывающую программу и оптимизируется, а не из-за того, что память времени выполнения упорядочивается с ней в этой позиции. Ни одно из назначений до mb() в lfq_enqueue не относится к объектам, которые другие потоки могут в настоящее время читать. Поэтому вам просто нужно убедиться, что у вас есть семантика выпуска в магазине, который публикуется. См. также en.wikipedia.org/wiki/Non-blocking_linked_list. - person Peter Cordes; 05.09.2018
comment
У меня произошел сбой, если я закомментировал строку lfq.c:8 lmb() + удалил lfq.h:16 volatile. Хоть ты и сказал, что это имеет смысл, но это не работа. - person Darkautism Nong; 05.09.2018
comment
Я думаю, что ваш CAS в enqueue на самом деле не бесполезен; вы используете его для атомарного обмена. Вы загружаете, затем CAS с этим значением, чтобы убедиться, что у вас правильное старое значение. Гораздо эффективнее было бы просто произвести обмен напрямую, чтобы обновить ctx->tail, чтобы он указывал на ваш новый узел, с инструкцией xchg (для этого тоже есть встроенная команда __sync). Но да, я думаю, что на самом деле так просто добавить в конец связанного списка, и нормально обновлять указатель old_tail->next только после обновления tail. - person Peter Cordes; 05.09.2018
comment
Если вы получаете сбои при удалении барьеров, это, вероятно, потому, что что-то где-то еще на самом деле небезопасно; может в dequeue. я не смотрел на это; выглядит сложно и странно. IDK, почему вы просто не CAS(&ctx->head, old_head, next) атомарно продвигаете head и не требуете старого заголовка для этого потока после проверки внутри цикла повтора, что next не является нулевым. Сбой CAS указывает на то, что другой считыватель выиграл гонку за этот узел. - person Peter Cordes; 05.09.2018
comment
Вы сказали старый коммит? github.com/darkautism/lfqueue/blob/ Извините, этот код версии не блокирует свободную очередь. Потому что вы замените пустой узел и должны заменить его в строке 56. Этот код версии на самом деле представляет собой мини-спин-блокировку. Так что CAS(&ctx->head, old_head, next) на самом деле не является безблокировочной очередью. - person Darkautism Nong; 05.09.2018
comment
Этот код использует ноль CAS, чтобы предотвратить замену пустой структуры. Если вы просто используете CAS(&ctx->head, old_head, next), вы замените пустую структуру. - person Darkautism Nong; 05.09.2018