Реализация атомарной операции
я использую атомарную операцию, предоставляемую SunOs в void *atomic_cas_ptr(volatile void *target, void *cmp, void *newval);
теперь, чтобы сделать пригодным для использования, я должен проверить, являются ли старые значения, возвращенные этой функцией и переданные функцией вызываемого абонента cmp, одинаковыми, если они есть, то операция успешна.
но у меня есть определенные сомнения: так как эта функция возвращает пустой указатель на старое значение, давайте назовем его void *old, и я передаю void *cmp, тогда мне нужно сравнить эти два старых и cmp, так как я собираюсь сравнить эти двое? и если при сравнении * старый изменился, то что я собираюсь делать?
по сути, я хочу деформировать эту функцию внутри другой функции, которая принимает эти три аргумента и возвращает либо true, либо false, которые указывают на успех или неудачу.
о CAS
Я читал, что неправильно называть это операцией без блокировки, так как она в конечном итоге берет блокировку на аппаратном уровне (блокировка на шине), это правильно, верно? Вот почему CAS является дорогостоящей операцией.
1 ответ
Возможно, объявление функции сбило вас с толку. Эта функция не возвращает указатель на старое значение (чего?), Но старое значение из памяти, указанной target
(который должен быть указателем на void*, т.е. void* volatile * target
).
Обычно, если примитив CAS возвращает старое значение, а не bool, вы проверяете успех CAS следующим образом:
void* atomic_ptr; // global atomically modified pointer
void* oldval, newval, comparand; // local variables
/* ... */
oldval = atomic_cas_ptr( (void*)&atomic_ptr, /* note that address is taken */
comparand, newval );
if( oldval == comparand ) {
// success
} else {
// failure
}
Поэтому, когда вы сравниваете old_val и сравнение, вы работаете с локальными переменными, которые не изменяются одновременно (в то время как глобальный atomic_ptr может быть изменен снова), и вы сравниваете значения указателя без разыменования.
Функция, которую вы хотите, должна быть такой:
bool my_atomic_cas_ptr(volatile void* target, void* comparand, void* newval)
{
return (comparand == atomic_cas_ptr(target, comparand, newval));
}
Обратите внимание, что, поскольку в некоторых алгоритмах старое значение (которое до CAS) должно быть известно, лучше иметь примитив CAS, возвращающий старое значение, а не bool, так как вы можете легко построить последнее из первого, в то время как противоположное более сложный и неэффективный (см. следующий код, который пытается получить правильное старое значение из примитива MacOS CAS, который возвращает bool).
void* CAS(void* volatile* target, void* comparand, void* newval)
{
while( !OSAtomicCompareAndSwapPtr(comparand, newval, target) ) {
void* snapshot = *target;
if( snapshot!=comparand ) return snapshot;
}
return comparand;
}
Что касается блокировки памяти шины CAS, то это зависит от аппаратного обеспечения. Это было верно для старых процессоров x86, но в современных системах x86 все по-другому. Во-первых, нет центрального автобуса; его заменили AMD HyperTransport и Intel QuickPath Interconnect. Во-вторых, в последних поколениях ЦП заблокированные инструкции не все сериализуются (см. Некоторые данные, показывающие, что заблокированные инструкции на разных адресах памяти не мешают). И, наконец, в общепринятом определении свобода блокировки является гарантией прогресса всей системы, а не отсутствия синхронизации сериализации.