Вопрос

Я столкнулся с ситуацией, когда мне нужна атомная сумма двух значений в памяти.Код, который я унаследовал, выглядит следующим образом:

int a = *MemoryLocationOne;
memory_fence();
int b = *MemoryLocationTwo;
return (a + b) == 0;

Отдельные чтения a и b являются атомарными, и все записи в других местах кода в эти две ячейки памяти также являются атомарными без блокировки.Однако проблема в том, что значения двух местоположений могут меняться и меняются между двумя чтениями.

Итак, как мне сделать эту операцию атомарной?Я знаю все о CAS, но он, как правило, предполагает только атомизацию операций чтения-изменения-записи, а это не совсем то, что я хочу здесь сделать.

Есть ли способ сделать это или лучше всего провести рефакторинг кода, чтобы мне нужно было проверять только одно значение?

Редактировать:Спасибо, я не упомянул, что хотел сделать это без блокировки в первой версии, но некоторые люди заметили это после моей второй версии.Я знаю, что никто не верит людям, когда они говорят подобные вещи, но я практически не умею пользоваться замками.Мне пришлось бы эмулировать мьютекс с помощью атомов, и это было бы больше работы, чем рефакторинг кода, чтобы отслеживать одно значение вместо двух.

На данный момент мой метод исследования предполагает использование того факта, что значения являются последовательными, и получение их атомарно с помощью 64-битного чтения, которое, я уверен, является атомарным на моих целевых платформах.Если у кого-то есть новые идеи, пожалуйста, поделитесь!Спасибо.

Это было полезно?

Решение

Если вам действительно нужно убедиться, что a и b не меняйте пока вы выполняете этот тест, тогда вам нужно использовать ту же синхронизацию для все доступ к a и b.Это ваш единственный выбор.Каждое чтение и каждая запись любого из этих значений должны использовать одну и ту же границу памяти, синхронизатор, семафор, блокировку временного интервала или любой другой используемый механизм.

Благодаря этому вы можете быть уверены в том, что если вы:

memory_fence_start();
int a = *MemoryLocationOne;
int b = *MemoryLocationTwo;
int test = (a + b) == 0;
memory_fence_stop();

return test;

затем a не изменится, пока вы читаете b.Но опять же, вам придется использовать тот же механизм синхронизации для все доступ к a и чтобы b.

Чтобы отразить более позднее изменение вашего вопроса о том, что вы ищете метод без блокировки, это полностью зависит от процессора, который вы используете, и от того, как долго a и b и от того, являются ли эти ячейки памяти последовательными и правильно ли выровнены.

Предполагая, что они расположены последовательно в памяти и по 32 бита каждый, и что ваш процессор имеет атомарное 64-битное чтение, тогда вы можете выполнить атомарное 64-битное чтение, чтобы прочитать два значения, проанализировать два значения из 64-битного значения. , посчитайте и верните то, что хотите вернуть.Предполагая, что вам никогда не понадобится атомарное обновление для "a и b в то же время", но только атомарные обновления "a"или"b" изолированно, то это будет делать то, что вы хотите, без блокировок.

Другие советы

Вам нужно будет убедиться, что везде, где любое из двух значений читается или записывается, они окружены барьером памяти (блокировкой или критической секцией).

// all reads...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
    a = *MemoryLocationOne;
    b = *MemoryLocationTwo;
}

...

// all writes...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
    *MemoryLocationOne = someValue;
    *MemoryLocationTwo = someOtherValue;
}

Если вы ориентируетесь на x86, вы можете использовать поддержку 64-битного сравнения/обмена и упаковать оба целых числа в одно 64-битное слово.

В Windows вы должны сделать это:

// Skipping ensuring padding.
union Data
{
     struct members
     {
         int a;
         int b;
     };

     LONGLONG _64bitData;  
};

Data* data;


Data captured;

do
{
    captured = *data;
    int result = captured.members.a + captured.members.b;
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData,
                    captured._64BitData,
                    captured._64bitData) != captured._64BitData);

Действительно некрасиво.Я бы предложил использовать замок - гораздо удобнее в обслуживании.

РЕДАКТИРОВАТЬ:Чтобы обновить и прочитать отдельные части:

data->members.a = 0;
fence();

data->members.b = 0;
fence();

int captured = data->members.a;

int captured = data->members.b;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top