Чи існує готова до замовлення безблокована черга або реалізація хешу в C ++ [закрито]


80

Я досить багато гуглив за чергою без блокування в C ++. Я знайшов трохи коду та кілька випробувань - але нічого, що я не зміг скомпілювати. Також буде вітатися хеш без замків.

РЕЗЮМЕ: Поки що у мене немає позитивної відповіді. Не існує бібліотеки "готової до виробництва", і напрочуд жодна з існуючих бібліотек не відповідає API контейнерів STL.


4
Visual Studio 2010 містить чергу без блокування в <concurrent_queue.h>
Рік

1
А ще є hash_map і unordered_map за адресою code.msdn.com/concrtextras
Рік

2
Зазначимо, що, що цікаво, термін "без замків" не обов'язково означає відсутність замків. Див. En.wikipedia.org/wiki/Non-blocking_algorithm для одного визначення.
Крістофер Джонсон,

8
Вау питання, яке задає питання про те, як вирішити типову, але складну проблему в багатопотоковому програмуванні, що має кілька рішень, викликало багато дискусій і заробило масу прихильників ... Потім через 9 років ви закриваєте це як тему. Дякуємо за ваш внесок у StackOverflow, NathanOliver, Sir E_net4 the Wise Downvoter, Jean-François Fabre, Machavity, and gre_gor / s
muusbolla

1
Я б сказав, що люди, які закрили питання, напевно, цього не розуміють.
Дерф Скрен

Відповіді:


40

Починаючи з 1.53, Boost забезпечує набір структур даних без блокування , включаючи черги, стеки та черги одного виробника / одного споживача (тобто кільцеві буфери).


boost :: lockfree :: queue працює лише з типами POD, і в більшості випадків це не зовсім корисно. Я впевнений, якби існував спосіб забезпечити більш гнучку структуру, би стимул її запровадив.
rahman

1
@rahman, де проблема в цьому? Якщо ви хочете передати що-небудь ще, особливо об’єкти з непрозорими, які, можливо, блокують побічні ефекти, ви не зрозуміли всієї мети дизайну, вільного від блокування.
Ichthyo

Чому boost забезпечує чергу та стек без блокування, обидва на основі пов’язаного списку. Але вони не надають список блокованих посилань?
Декінг

25

Відправною точкою буде будь-яка стаття DDJ Herb Sutter для одного виробника та споживача, або для кількох . Код, який він надає (у рядку, починаючи з другої сторінки кожної статті), використовує атомарний тип шаблону C ++ 0x <T>; яку можна імітувати, використовуючи міжпроцесорну бібліотеку Boost.

Код підсилення закопаний в глибині межпроцессной бібліотеки, але прочитавши відповідний заголовочний файл (atomic.hpp) реалізації для необхідних операцій порівняння та обміну в системах, які я знайомий із виглядом звуку.


1
Стіве, мене також цікавлять атомні реалізації Boost, але вони, схоже, містяться в деталях Interprocess / і не задокументовані. Чи безпечно їх використовувати в будь-якому випадку? Дякую!
Кім Гресман,

Я дуже добре знаю статті Герба Саттера - де ви знайшли джерела? Вони не публікуються DDJ і на його сайті (а може, я сліпий?).
RED SOFT ADAIR

1
Код є рядковим у цих статтях, починаючи з відповідних других сторінок.
Steve Gilham

3
Якщо ви хочете по-справжньому код без замків, для кількох викрадачів або споживачів, тоді я вийшов. Приклад черги кількох виробників Саттера не є вільним від блокування - існує замок для серіалізації виробників та замок для серіалізації споживачів. Якщо ви можете знайти його, я б також зацікавився цим.
Steve Gilham

1
Існує проект boost :: lockfree за адресою tim.klingt.org/git?p=boost_lockfree.git ; що ви можете подивитися. Однією з його цілей є надання атомарних примітивів не :: деталі :: версії.
sstock

17

Так!

Я написав чергу без замків . Він має Особливості ™:

  • Повністю без очікування (без циклів CAS)
  • Надзвичайно швидко (понад сто мільйонів операцій з чергування / вилучення в секунду)
  • Використовує семантику переміщення C ++ 11
  • Зростає за потребою (але лише за бажанням)
  • Забезпечує управління блокуванням пам'яті для елементів (використовуючи попередньо виділені суміжні блоки)
  • Автономний (два заголовки плюс ліцензія та readme)
  • Компілює під MSVC2010 +, Intel ICC 13 та GCC 4.7.2 (і повинен працювати під будь-яким повністю сумісним компілятором C ++ 11)

Він доступний на GitHub за спрощеною ліцензією BSD (сміливо розмовляйте!).

Застереження:

  • Тільки для архітектури одного споживача одного виробника (тобто дві нитки)
  • Ретельно протестований на x86 (-64) і повинен працювати на ARM, PowerPC та інших центральних процесорах, де вирівняні цілі числа власного розміру та навантаження та сховища покажчиків є природно атомними, але не пройшли випробування на місцях на процесорах, що не є x86 (якщо хтось має один, щоб перевірити це, дайте мені знати)
  • Не маємо уявлення, чи порушені будь-які патенти (використовуйте на власний ризик тощо). Зауважте, що я сам розробляв та реалізовував його з нуля.

2
Звучить дуже добре, але для використання справжньої багатопоточності потрібно декілька виробників та / або декількох споживачів.
RED SOFT ADAIR

2
@RED: Залежить від програми. Мені потрібно було лише одного виробника / споживача, тому це все, що я побудував ;-)
Камерон,

@Cameron: Чудові речі! Ви порівняли свою чергу з глупством Facebook ProducerConsumerQueue ? Я зробив це, використовуючи ваш тестовий код, і, здається, він значно перевершує ваш RWQ і SPSC Дмитра. Я працюю в OS X 10.8.3 з 3,06 ГГц Core 2 Duo (T9900) і скомпілював код за допомогою Clang з -O3. Я зробив це, оскільки зараз переглядаю чергу одного виробника / одного споживача для одного зі своїх проектів, і я вважав ваш кандидат кандидатом :)
Андре Невес,

@ André: Я щойно перевірив :-) Нерозумність Facebook трохи швидша за мою, коли виводиться з порожньої черги, і дещо повільніше, коли вибуває з непорожньої черги в одному потоці. Усі інші операції мають майже однакову швидкість (це було з g ++ -O3 на віртуальній машині). Який розмір ви використовуєте для черги безумства? (Я використовував МАКС.) І у мене, і у Дмитра зростає потреба, тоді як дурість виправлена ​​- і, звичайно, найшвидша операція в черзі, коли місця немає, і вона просто виходить з ладу. Дивлячись на код, Folly's, схоже, використовує ті самі ідеї, що і моя, але без зміни розміру.
Камерон

@ André: О, ще одну річ, про яку я забув згадати - з моїм тестом тесту, тест "Raw empty remove" робить на сьогоднішній день найбільше ітерацій (оскільки це настільки просто, що потрібно більше для отримання вимірного результату), який має тенденцію щоб непропорційно вплинути на кінцеві "середні операційні показники". Мультиплікатори (і плоскі значення часу), як правило, більш корисні. У будь-якому випадку, на таких швидкостях всі ці черги будуть досить швидкими, якщо їх насправді використовують для чогось більш м’якого, ніж мої безглузді синтетичні орієнтири ;-)
Камерон,

15

Здається, у Facebook Folly є безкоштовні структури даних на основі C ++ 11 <atomic>:

Я б наважився сказати, що вони зараз використовуються у виробництві, тож я думаю, їх можна сміливо використовувати в інших проектах.

Ура!


Вони також мають чергу MPMC. що вони стверджують, що "за бажанням блокують". Здається, це не є частиною їх регулярної документації, не впевнений, чи рекомендується це використовувати.
Расті Шеклфорд,

11

Є така бібліотека, але вона є в C.

Перенесення на C ++ має бути простим.

http://www.liblfds.org


10

Перевіривши більшість поданих відповідей, я можу лише стверджувати:

Відповідь - НІ .

Немає такої речі, яка могла б бути використана прямо з коробки.


4
100% правильно. Я дійшов того самого результату за допомогою групи новин comp.programming.threads. Однією з причин є те, що область безблокувальних структур даних - це патентна шахта. Тож навіть комерційні бібліотеки, такі як Intels, уникають цього.
Лотар

Це C, а не C ++. Будь ласка, прочитайте питання перед голосуванням.
RED SOFT ADAIR

Вибачення. Я зауважую, ТО не дозволить мені скасувати свій голос, оскільки він вважає, що голосування занадто старе. Я думаю, що розробникам SO потрібно зробити більше - вони, здається, додають все більшу кількість непотрібної поведінки.

3
Чому ця відповідь набирає так багато голосів. Питання можна легко відредагувати. Або це може бути в коментарі.
користувач


6

Найближче, що мені відомо, - це взаємозв’язані списки з єдиним зв’язком Windows . Звичайно, це лише Windows.


Ого - здається, це все. Мені знадобиться трохи часу, щоб перевірити це (наразі я не можу це зробити), але я повернусь до вас.
RED SOFT ADAIR

Заблокований єдинозв’язаний список - чудовий інструмент, але, на жаль, це не FIFO.
ali_bahoo

Як я пам’ятаю, це не належний список. Ви не можете від’єднати довільні елементи; єдине, що ви можете зробити, це видалити весь список. Можливо, це

5

Якщо у вас є черга декількох виробників / одного споживача / FIFO, ви можете легко створити один LockFree, використовуючи SLIST або тривіальний Lock Free LIFO стек. Те, що ви робите, - це створити другий «приватний» стек для споживача (який також можна зробити як СПИСОК для простоти або будь-яку іншу вибрану вами модель стека). Споживач викидає речі з приватного стеку. Кожного разу, коли приватний LIFO вичерпується, ви робите флеш, а не вискакуєте спільний паралельний SLIST (захоплюючи весь ланцюжок SLIST), а потім проходите список Flushed у порядку, висуваючи елементи на приватний стек.

Це працює для одного виробника / одного споживача та для декількох виробників / одного споживача.

Однак це не працює для випадків із кількома споживачами (як з одним виробником, так і з декількома виробниками).

Окрім того, що стосується хеш-таблиць, вони є ідеальним кандидатом для «розмежування», яке просто поділяє хеш на сегменти, що мають блокування на сегменти кешу. Ось як це робить паралельна бібліотека Java (використовуючи 32 смуги). Якщо у вас є легкий замок зчитувача-записувача, до хеш-таблиці можна отримати одночасний доступ для одночасного читання, і ви зупинитесь лише тоді, коли запис відбувається на спірних смугах (і, можливо, якщо ви дозволяєте нарощувати хеш-таблицю).

Якщо ви прокручуєте свої власні, переконайтеся, що ви переплетете свої замки за допомогою хеш-записів, а не покладете всі свої замки в масив поруч один з одним, щоб ви мали менше шансів на обмін помилками.


Дякую за вашу відповідь. Я шукаю рішення / шаблон, готовий до виробництва, на C ++. Я не хочу котити свою власну. Ви знаєте таку реалізацію?
RED SOFT ADAIR

4

Я можу затриматись із цим.

Відсутність рішень (на запитання) було головним чином пов'язано з важливою проблемою в C ++ (до C ++ 0x / 11): C ++ не має (не має) одночасної моделі пам'яті.

Тепер, використовуючи std :: atomic, ви можете контролювати проблеми впорядкування пам'яті та мати належні операції порівняння та обміну. Я написав собі реалізацію безблокової черги Micheal & Scott (PODC96) з використанням C ++ 11 та покажчиків небезпеки Micheal (IEEE TPDS 2004), щоб уникнути проблем із безкоштовним звільненням та ABA. Це працює нормально, але це швидке та брудне впровадження, і я не задоволений фактичними показниками. Код доступний на bitbucket: LockFreeExperiment

Також можна запровадити чергу без замків без вказівників на небезпеку, використовуючи подвійні слова CAS (але 64-бітні версії будуть можливі лише на x86-64 за допомогою cmpxchg16b), я маю допис у блозі про це (з неперевіреним кодом черги) тут : Впровадження загального порівняння та заміни двох слів для x86 / x86-64 (блог LSE.)

Мій власний орієнтир показує мені, що черга з подвійним блокуванням (також у статті Micheal & Scott 1996) працює так само, як і без блокування (я не досяг достатньої кількості суперечок, щоб заблоковані структури даних мали проблеми з продуктивністю, але мій стенд занадто легкий для зараз), і паралельна черга від TBB від Intel здається ще кращою (вдвічі швидшою) для відносно невеликої кількості (залежно від операційної системи, у FreeBSD 9, найнижча межа, яку я знайшов на даний момент, ця кількість становить 8 потоків на i7 з 4 ядрами, а отже 8 логічними процесорами) потоків і мають дуже дивну поведінку (час виконання мого простого тесту переміщується з секунд на години!)

Інші обмеження щодо безблокувальних черг, що відповідають стилю STL: наявність ітераторів у безблокованій черзі не має сенсу.


3

А потім з’явилися будівельні блоки Intel Threading . І деякий час це було добре.

PS: ви шукаєте concurrent_queue і concurrent_hash_map



1
Я знаю, що стосується суворого сенсу безблокування, але я тим не менше думав, що це може допомогти ОП у вирішенні його проблеми, оскільки річ без замків - це лише деталь реалізації. Я думав, що він шукає колекції, які добре працюють при одночасному доступі.
Едуар А.,

Річ без замків - це не просто доповнення деталей. Це зовсім інший звір.
arunmoezhi

1

Наскільки мені відомо, такого ще немає у відкритому доступі. Одне питання, яке повинен вирішити реалізатор, полягає в тому, що вам потрібен розподільник пам’яті без блокування, який існує, хоча я не можу зараз знайти посилання.


Для мене немає сенсу, чому доступний розподільник пам'яті. Просто використовуйте структури даних із власними вказівниками (ви знаєте старий добрий спосіб, поки не збожеволіли від контейнерів і не втратили навичок навіть реалізовувати прості хеш-таблиці).
Лотар

1

Далі йдеться зі статті Herb Sutter про Одночасну чергову безпеку без блокування http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1 . Я вніс деякі зміни, такі як компілятор, що впорядковує речі. Для компіляції цього коду потрібен GCC v4.4 +.

#include <atomic>
#include <iostream>
using namespace std;

//compile with g++ setting -std=c++0x

#define CACHE_LINE_SIZE 64

template <typename T>
struct LowLockQueue {
private:
    struct Node {
    Node( T* val ) : value(val), next(nullptr) { }
    T* value;
    atomic<Node*> next;
    char pad[CACHE_LINE_SIZE - sizeof(T*)- sizeof(atomic<Node*>)];
    };
    char pad0[CACHE_LINE_SIZE];

// for one consumer at a time
    Node* first;

    char pad1[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among consumers
    atomic<bool> consumerLock;

    char pad2[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

// for one producer at a time
    Node* last;

    char pad3[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among producers
    atomic<bool> producerLock;

    char pad4[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

public:
    LowLockQueue() {
    first = last = new Node( nullptr );
    producerLock = consumerLock = false;
    }
    ~LowLockQueue() {
    while( first != nullptr ) {      // release the list
        Node* tmp = first;
        first = tmp->next;
        delete tmp->value;       // no-op if null
        delete tmp;
    }
    }

    void Produce( const T& t ) {
    Node* tmp = new Node( new T(t) );
    asm volatile("" ::: "memory");                            // prevent compiler reordering
    while( producerLock.exchange(true) )
        { }   // acquire exclusivity
    last->next = tmp;         // publish to consumers
    last = tmp;             // swing last forward
    producerLock = false;       // release exclusivity
    }

    bool Consume( T& result ) {
    while( consumerLock.exchange(true) )
        { }    // acquire exclusivity
    Node* theFirst = first;
    Node* theNext = first-> next;
    if( theNext != nullptr ) {   // if queue is nonempty
        T* val = theNext->value;    // take it out
        asm volatile("" ::: "memory");                            // prevent compiler reordering
        theNext->value = nullptr;  // of the Node
        first = theNext;          // swing first forward
        consumerLock = false;             // release exclusivity
        result = *val;    // now copy it back
        delete val;       // clean up the value
        delete theFirst;      // and the old dummy
        return true;      // and report success
    }
    consumerLock = false;   // release exclusivity
    return false;                  // report queue was empty
    }
};

int main(int argc, char* argv[])
{
    //Instead of this Mambo Jambo one can use pthreads in Linux to test comprehensively
LowLockQueue<int> Q;
Q.Produce(2);
Q.Produce(6);

int a;
Q.Consume(a);
cout<< a << endl;
Q.Consume(a);
cout<< a << endl;

return 0;
}

4
Це не безкоштовно. Звичайно, він не використовує блокування, передбачене операційною системою, але те, як він обертається на (наприклад) "atomic <bool> consumerLock", безумовно, є поведінкою блокування. Якщо потік виходить з ладу, поки він утримує один із цих замків, більше не можна виконати жодної роботи. Навіть сам Герб так говорить (я думаю, на сторінці 4 цієї статті).
James Caccese,


0

Я писав це колись, мабуть, ще в 2010 році, я впевнений, за допомогою різних посилань. Це багатовиробничий єдиний споживач.

template <typename T>
class MPSCLockFreeQueue 
{
private:
    struct Node 
    {
        Node( T val ) : value(val), next(NULL) { }
        T value;
        Node* next;
    };
    Node * Head;               
    __declspec(align(4)) Node * InsertionPoint;  //__declspec(align(4)) forces 32bit alignment this must be changed for 64bit when appropriate.

public:
    MPSCLockFreeQueue() 
    {
        InsertionPoint = new Node( T() );
        Head = InsertionPoint;
    }
    ~MPSCLockFreeQueue() 
    {
        // release the list
        T result;
        while( Consume(result) ) 
        {   
            //The list should be cleaned up before the destructor is called as there is no way to know whether or not to delete the value.
            //So we just do our best.
        }
    }

    void Produce( const T& t ) 
    {
        Node * node = new Node(t);
        Node * oldInsertionPoint = (Node *) InterLockedxChange((volatile void **)&InsertionPoint,node);
        oldInsertionPoint->next = node;
    }

    bool Consume( T& result ) 
    {
        if (Head->next)
        {
            Node * oldHead = Head;
            Head = Head->next;
            delete oldHead;
            result = Head->value;
            return true;
        }       
        return false;               // else report empty
    }

};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.