2015-05-14 2 views
-1

Что мне нужно делать сегодня с атомными операциями, довольно сложно, и мне нужна помощь. В двух словах, я хотел бы выполнить следующую операцию атомарной:Сравните некоторые атомные обмены

/* begin of atomicity */ 
if (--counter == 0) 
{ 
    Widget* widget = nullptr; 

    swap(some_widget, widget); 

    /* end of atomicity */ 

    // use local 'widget' here 
} 
/* end of atomicity */ 

Я знаю C++ 11 Атомикс и встроенных функций. Предыдущий код только pseudo и предназначен для того, чтобы показать, что я хотел бы произойти атомарно. Я знаю, что одно решение заключается в том, чтобы счетчик имел состояние «Переход» (пример 0xFFFFFFFF) и менял его значение сначала на этот переход, а затем на новое значение: он по существу был бы аналогичен спин-блокировке. Любая идея о том, как это сделать эффективно (т. Е. Нет шпиндельных замков, нет мьютексов и, возможно, нет ожиданий)?

спасибо.

+0

Там вы два **/\ * конец атомарностью \ */**, что один * правда * конец? Я сомневаюсь, что любой способ выполнить несколько операторов в атоме без использования блокировки. – davidshen84

+0

Кроме того, вы не хотите * delete * 'some_widget', вы просто хотите присвоить его' nullptr'? – Barry

ответ

0

Предполагая, что C++ 11, вы хотите сделать атомный fetch_and_add, а затем работать с результатами.

Например:

std::atomic<int> counter = 20; 

if (counter.fetch_sub(1) == 1) /* return value is old value */ 
{ 
    Widget* widget = nullptr; 
    swap(some_widget, widget); 
} 

Если счетчик используется между потоками, мы должны убедиться, что компилятор и аппаратное обеспечение не испортит порядок памяти. Заказ по умолчанию для std::atomic<>.fetch_sub() - std::memory_order_seq_cst, который будет прекрасно работать, но если вы хотите выиграть, например. ARM64, вы можете уменьшить до std::memory_order_acquire.

Чтобы сделать операцию свопинга в приведенной выше схеме, нам нужен замок. Интересно, что это соответствует шаблону «двойной проверки блокировки», который на кого-то недоволен, но здесь весьма полезен - для большинства операций вы не будете страдать от блокировки, но когда это считается, оно есть. Вот как:

std::atomic<int> counter = 20; 
std::mutex counterLock; 
std::atomic<Widget*> some_widget; 

... 

if (counter.load(std::memory_order_acquire) > 5) /* first check whether we're "close enough" */ 
{ 
    /* only lock when needed */ 
    std::lock_guard<std::mutex> templock(counterLock); /* free lock whenever we exit this scope */ 
    if (counter.fetch_sub(1) == 1) /* return value is old value */ 
    { 
    Widget* oldwidgetpr = some_widget.exchange(nullptr, std::memory_ordering_release); 
    /* do whatever you need to do with the oldwidgetptr */ 
    } 
} 
else 
    counter.fetch_sub(1); 
+0

Это именно тот код, который у меня есть сейчас, но декремент и своп не являются атомарными. Сразу после декрементирования другой поток может изменить указатель «some_widget», и у меня есть гонка. – keebus

+0

В этом случае, почему бы не пойти на подход с двойной проверкой? это позволит быстро уменьшаться без блокировок, но избавит вас от проблем с заменой. Я закодирую его, а затем опубликую его. –

+0

Отредактировал мой ответ, чтобы включить вышеизложенное. –

Смежные вопросы