Легкие спин-блокировки создаются из атомарных операций GCC?

Я хотел бы минимизировать синхронизацию и написать свободный от блокировок код, если это возможно, в моем проекте. Когда абсолютно необходимый я хотел бы занять место, легкие спин-блокировки создали из атомарных операций для pthread и win32 взаимоисключающих блокировок. Мое понимание - то, что они - системные вызовы внизу и могли вызвать контекстное переключение (который может быть ненужным для очень быстрых критических разделов, где просто вращение несколько раз было бы предпочтительно).

Атомарные операции, к которым я обращаюсь, хорошо документируются здесь: http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Atomic-Builtins.html

Вот пример для иллюстрирования то, о чем я говорю. Вообразите RB-дерево с несколькими средствами чтения и устройствами записи возможным. RBTree:: существует (), только для чтения и ориентирован на многопотоковое исполнение, RBTree:: вставьте (), потребовал бы, чтобы эксклюзивный доступ единственным устройством записи (и никакие читатели) был безопасен. Некоторый код:

class IntSetTest
{
private:
    unsigned short lock;
    RBTree* myset;

public:
    // ...

    void add_number(int n)
    {
        // Aquire once locked==false (atomic)
        while (__sync_bool_compare_and_swap(&lock, 0, 0xffff) == false);

        // Perform a thread-unsafe operation on the set
        myset->insert(n);

        // Unlock (atomic)
        __sync_bool_compare_and_swap(&lock, 0xffff, 0);
    }

    bool check_number(int n)
    {
        // Increment once the lock is below 0xffff
        u16 savedlock = lock;
        while (savedlock == 0xffff || __sync_bool_compare_and_swap(&lock, savedlock, savedlock+1) == false)
            savedlock = lock;

        // Perform read-only operation    
        bool exists = tree->exists(n);

        // Decrement
        savedlock = lock;
        while (__sync_bool_compare_and_swap(&lock, savedlock, savedlock-1) == false)
            savedlock = lock;

        return exists;
    }
};

(позволяет, предполагают, что это не должно быть безопасно от исключения),

Этот код действительно ориентирован на многопотоковое исполнение? Там какие-либо профессионалы/недостатки к этой идее? Совет? Является использование спин-блокировок как это плохой идеей, если потоки не действительно параллельны?

Заранее спасибо. ;)

11
задан Thomas 27 April 2010 в 01:18
поделиться

2 ответа

Вам нужен квалификатор volatile для блокировки , и я бы также сделал его sig_atomic_t . Без квалификатора volatile этот код:

    u16 savedlock = lock;
    while (savedlock == 0xffff || __sync_bool_compare_and_swap(&lock, savedlock, savedlock+1) == false)
        savedlock = lock;

не может повторно считывать блокировку при обновлении сохраненной блокировки в теле цикла while. Рассмотрим случай, когда блокировка равна 0xffff. Тогда сохраненная блокировка будет иметь значение 0xffff перед проверкой условия цикла, поэтому условие , а будет закорочено перед вызовом __ sync_bool_compare_and_swap . Поскольку __ sync_bool_compare_and_swap не был вызван, компилятор не сталкивается с барьером памяти, поэтому можно разумно предположить, что значение lock не изменилось под вами, и избежать повторного- загружая его в savelock .

Re: sig_atomic_t , здесь есть достойное обсуждение . Те же соображения, которые применяются к обработчикам сигналов, также применимы к потокам.

Я предполагаю, что с этими изменениями ваш код будет потокобезопасным.Я все же рекомендую использовать мьютексы, поскольку вы действительно не знаете, сколько времени займет вставка вашего RB-дерева в общем случае (согласно моим предыдущим комментариям под вопросом).

4
ответ дан 3 December 2019 в 11:36
поделиться

Возможно, стоит отметить, что если вы используете мьютексы Win32, то начиная с Vista и далее вам предоставляется пул потоков. В зависимости от того, для чего вы используете дерево RB, вы можете заменить его.

Также вы должны помнить, что атомарные операции не очень быстрые. Microsoft заявила, что это пара сотен циклов каждый.

Вместо того, чтобы пытаться «защитить» функцию таким образом, было бы гораздо эффективнее просто синхронизировать потоки, либо переходя на подход SIMD / пул потоков, либо просто использовать мьютекс.

Но, конечно, не видя вашего кода, я не могу больше комментировать. Проблема с многопоточностью в том, что вам нужно увидеть чью-то модель целиком, чтобы понять ее.

1
ответ дан 3 December 2019 в 11:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: