2009-04-10 2 views
6

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

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

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

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

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

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

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

ответ

3

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

+0

Они случаются быть последовательными 32-битные адреса и процессоры мне нужно код для работы на есть атомный 64-битный читает, если правильно выровнены, так это похоже на путь вперед. –

+0

Ах, в этом случае вы можете либо написать какую-нибудь сборку, либо просто проверить сборку вашего компилятора, чтобы убедиться, что использование 64-битного типа сделает все правильно. В этом случае вы можете отключиться. – Eddie

3

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

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

...

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

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

+0

Даже если процессор имел двойное атомное считывание, это помогло бы, если бы два указанных адреса памяти были правильно выровнены и последовательны. – Eddie

+0

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

+0

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

3

Если вы ориентируетесь на x86, вы можете использовать 64-разрядную поддержку сравнения и обмена и упаковать оба int в одно 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; 
+0

Это правда, но вы ввернуты, когда вам нужно обновить только один из этих элементов. –

+1

Нет, вы делаете базовые атомарные чтения/записи в 32-битные части. – Michael

+1

И в случае, если это было непонятно, я должен был показать, как это можно сделать - я всегда рекомендую избегать алгоритмов блокировки. – Michael

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