Я хотел бы минимизировать синхронизацию и написать свободный от блокировок код, если это возможно, в моем проекте. Когда абсолютно необходимый я хотел бы занять место, легкие спин-блокировки создали из атомарных операций для 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;
}
};
(позволяет, предполагают, что это не должно быть безопасно от исключения),
Этот код действительно ориентирован на многопотоковое исполнение? Там какие-либо профессионалы/недостатки к этой идее? Совет? Является использование спин-блокировок как это плохой идеей, если потоки не действительно параллельны?
Заранее спасибо. ;)
Вам нужен квалификатор 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-дерева в общем случае (согласно моим предыдущим комментариям под вопросом).
Возможно, стоит отметить, что если вы используете мьютексы Win32, то начиная с Vista и далее вам предоставляется пул потоков. В зависимости от того, для чего вы используете дерево RB, вы можете заменить его.
Также вы должны помнить, что атомарные операции не очень быстрые. Microsoft заявила, что это пара сотен циклов каждый.
Вместо того, чтобы пытаться «защитить» функцию таким образом, было бы гораздо эффективнее просто синхронизировать потоки, либо переходя на подход SIMD / пул потоков, либо просто использовать мьютекс.
Но, конечно, не видя вашего кода, я не могу больше комментировать. Проблема с многопоточностью в том, что вам нужно увидеть чью-то модель целиком, чтобы понять ее.