реализация атомарной операции - PullRequest
1 голос
/ 17 июня 2011


Я использую атомарную операцию, предоставленную SunOs в, что составляет
void *atomic_cas_ptr(volatile void *target, void *cmp, void *newval);

, чтобы сделать его пригодным для использования, я должен проверить, совпадают ли старые значения, возвращаемые этой функцией и передаваемые функцией вызываемого абонента cmp, если они есть, то операция успешна.
но у меня есть определенные сомнения: так как эта функция возвращает пустой указатель на старое значение, давайте назовем его void * old, и я передаю void * cmp, тогда мне нужно сравнить эти два старых и cmp, так как я собираюсьсравнить эти два?и если при сравнении * старый изменился, то что я собираюсь делать?
По сути, я хочу деформировать эту функцию внутри другой функции, которая принимает эти три аргумента и возвращает либо true, либо false, которые указывают на успех или неудачу.
о CAS, я читал, что неправильно называть это операцией без блокировки, так как она в конечном итоге берет блокировку на аппаратном уровне (блокировка на шине), это правильно, верно?Вот почему CAS является дорогостоящей операцией.

1 Ответ

2 голосов
/ 19 июня 2011

Возможно, объявление функции смутило вас. Эта функция не возвращает указатель на старое значение (чего?), Но старое значение из памяти указывает 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 , который возвращает логическое значение).

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. Во-вторых, в последних поколениях ЦП заблокированные инструкции не все сериализуются (см. некоторые данные , показывающие, что заблокированные инструкции на разных адресах памяти не мешают). И наконец, в общепринятом определении свобода блокировки является гарантией прогресса всей системы, а не отсутствия сериализационной синхронизации .

...