Есть ли способ сделать два чтения атомарными? - PullRequest
6 голосов
/ 10 апреля 2009

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

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

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

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

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

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

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

Ответы [ 4 ]

3 голосов
/ 10 апреля 2009

Если вы ориентируетесь на 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);

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

EDIT: Чтобы обновить и прочитать отдельные части:

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

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

int captured = data->members.a;

int captured = data->members.b;
3 голосов
/ 10 апреля 2009

Если вам действительно нужно убедиться, что 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. Но опять же, вы должны использовать один и тот же механизм синхронизации для доступа all к a и b.

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

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

3 голосов
/ 10 апреля 2009

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

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

...

// all writes...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
    *MemoryLocationOne = someValue;
    *MemoryLocationTwo = someOtherValue;
}
1 голос
/ 10 апреля 2009

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

...