Как гарантировать, что 64-битные записи являются атомарными? - PullRequest
16 голосов
/ 17 сентября 2008

Когда 64-разрядные записи могут быть гарантированно атомарными при программировании на C на платформе Intel x86 (в частности, Mac на базе Intel Mac с MacOSX 10.4 с использованием компилятора Intel)? Например:

unsigned long long int y;
y = 0xfedcba87654321ULL;
/* ... a bunch of other time-consuming stuff happens... */
y = 0x12345678abcdefULL;

Если другой поток проверяет значение y после завершения первого присваивания y, я хотел бы убедиться, что он видит либо значение 0xfedcba87654321, либо значение 0x12345678abcdef, а не какое-либо их сочетание. Я хотел бы сделать это без какой-либо блокировки, и, если возможно, без какого-либо дополнительного кода. Я надеюсь, что при использовании 64-разрядного компилятора (64-разрядного компилятора Intel) в операционной системе, способной поддерживать 64-разрядный код (MacOSX 10.4), эти 64-разрядные записи будут атомарными. Это всегда так?

Ответы [ 8 ]

41 голосов
/ 17 сентября 2008

Лучше всего избегать попыток построить свою собственную систему из примитивов, а вместо этого использовать блокировку, если только действительно не обнаружится как горячая точка при профилировании. (Если вы думаете, что можете быть умным и избегать блокировок, не делайте этого. Нет. Это общее «вы», которое включает меня и всех остальных.) Вы должны как минимум использовать спин-блокировку, см. spinlock ( 3) . И что бы вы ни делали, не пытайтесь реализовать "свои" блокировки. Вы поймете это неправильно.

В конечном счете, вам нужно использовать любые блокировки или атомарные операции, которые предоставляет ваша операционная система. Получить такие вещи точно правильно во во всех случаях очень сложно . Часто это может включать в себя знание таких вещей, как ошибки для конкретных версий конкретного процессора. («О, версия 2.0 этого процессора не выполняла отслеживание когерентности кеша в нужное время, это исправлено в версии 2.0.1, но в 2.0 вам нужно вставить NOP.») Просто добавьте volatile Ключевое слово для переменной в C почти всегда недостаточно.

В Mac OS X это означает, что вам нужно использовать функции, перечисленные в atomic (3) , для выполнения действительно атомарных операций между всеми процессорами на 32-битных, 64-битных и указателях размеры. (Используйте последний для любых атомарных операций над указателями, чтобы вы автоматически поддерживали 32/64-битную совместимость.) Это происходит независимо от того, хотите ли вы выполнять такие вещи, как атомарное сравнение-и-своп, увеличение / уменьшение, блокировка вращения или стек / очередь управление. К счастью, функции spinlock (3) , atomic (3) и барьер (3) должны работать корректно на всех процессорах, поддерживаемых Mac OS X .

13 голосов
/ 17 сентября 2008

На x86_64 и компилятор Intel, и gcc поддерживают некоторые встроенные атомарные функции. Вот документация gcc о них: http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html

Документы компилятора Intel также говорят о них здесь: http://softwarecommunity.intel.com/isn/downloads/softwareproducts/pdfs/347603.pdf (стр. 164 или около того).

11 голосов
/ 17 сентября 2008

В соответствии с главой 7 Часть 3А - Руководство по системному программированию из руководств по для процессоров Intel , доступ к четырем словам будет осуществляться атомарно, если выровнен по 64-битной границе, на Pentium или новее, и не выровнены (если все еще в пределах строки кэша) на P6 или новее. Вы должны использовать volatile, чтобы компилятор не пытался кэшировать запись в переменную, и вам может потребоваться использовать процедуру ограничения памяти, чтобы гарантировать, что запись происходит в правильном порядке.

Если вам нужно основать значение, записанное на существующем значении, вам следует использовать функции блокировки вашей операционной системы (например, в Windows имеется InterlockedIncrement64).

10 голосов
/ 17 сентября 2008

В Intel MacOSX вы можете использовать встроенную систему атомарных операций. Для 32- или 64-разрядных целых чисел не предусмотрено ни одного атомарного метода get или set, но вы можете создать его из предоставленного CompareAndSwap. Вы можете искать документацию XCode для различных функций OSAtomic. Я написал 64-битную версию ниже. 32-битная версия может быть выполнена с помощью функций с аналогичными именами.

#include <libkern/OSAtomic.h>
// bool OSAtomicCompareAndSwap64Barrier(int64_t oldValue, int64_t newValue, int64_t *theValue);

void AtomicSet(uint64_t *target, uint64_t new_value)
{
    while (true)
    {
        uint64_t old_value = *target;
        if (OSAtomicCompareAndSwap64Barrier(old_value, new_value, target)) return;
    }
}

uint64_t AtomicGet(uint64_t *target)
{
    while (true)
    {
        int64 value = *target;
        if (OSAtomicCompareAndSwap64Barrier(value, value, target)) return value;
    }
}

Обратите внимание, что функции Apple OSAtomicCompareAndSwap атомарно выполняют операцию:

if (*theValue != oldValue) return false;
*theValue = newValue;
return true;

Мы используем это в примере выше, чтобы создать метод Set, сначала захватывая старое значение, а затем пытаясь поменять значение целевой памяти. Если своп успешен, это означает, что значение памяти все еще является старым значением во время свопа, и ему дается новое значение во время свопа (который сам по себе является атомарным), так что мы сделали. Если это не удалось, тогда какой-то другой поток вмешался, изменив промежуточное значение, когда мы его захватили и когда мы попытались сбросить его. Если это произойдет, мы можем просто выполнить цикл и повторить попытку с минимальным штрафом.

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

Я не проверял это с моим компилятором, поэтому прошу прощения за любые опечатки.

Вы упомянули OSX специально, но в случае, если вам нужно работать на других платформах, в Windows есть несколько функций Interlocked *, и вы можете найти их в документации MSDN. Некоторые из них работают в Windows 2000 Pro и более поздних версиях, а некоторые (в частности, некоторые из 64-разрядных функций) являются новыми в Vista. На других платформах GCC версии 4.1 и новее имеют различные функции __sync *, такие как __sync_fetch_and_add (). Для других систем вам может потребоваться использовать сборку, и вы можете найти некоторые реализации в браузере SVN для проекта HaikuOS, внутри src / system / libroot / os / arch.

6 голосов
/ 30 октября 2009

В X86 самый быстрый способ атомарной записи выровненного 64-битного значения - это использование FISTP. Для невыровненных значений вам нужно использовать CAS2 (_InterlockedExchange64). Операция CAS2 довольно медленная из-за BUSLOCK, поэтому часто бывает проще проверить выравнивание и выполнить версию FISTP для выровненных адресов. Действительно, именно таким образом многопоточные строительные блоки Intel реализуют 64-разрядные записи Atomic.

2 голосов
/ 17 сентября 2008

Если вы хотите сделать что-то подобное для межпроцессного или межпроцессного взаимодействия, то вам нужно иметь больше, чем просто атомарную гарантию чтения / записи. В вашем примере кажется, что вы хотите, чтобы записанные значения указывали на то, что некоторая работа выполняется и / или была завершена. Вам нужно будет сделать несколько вещей, не все из которых являются переносимыми, чтобы убедиться, что компилятор сделал все в том порядке, в котором вы этого хотите (ключевое слово volatile может помочь в некоторой степени), и чтобы память была согласованной. Современные процессоры и кеши могут выполнять работу неупорядоченно, без ведома компилятора, поэтому вам действительно нужна некоторая поддержка платформы (т. Е. Блокировки или привязанные к платформе API-интерфейсы), чтобы делать то, что вы хотите делать.

«Забор памяти» или «барьер памяти» - это термины, которые вы, возможно, захотите исследовать.

1 голос
/ 07 апреля 2015

Последняя версия ISO C (C11) определяет набор атомарных операций, включая atomic_store(_explicit). Смотрите, например эта страница для получения дополнительной информации.

Второй наиболее переносимой реализацией атомики являются встроенные функции GCC, о которых уже упоминалось. Я считаю, что они полностью поддерживаются компиляторами GCC, Clang, Intel и IBM и - по состоянию на последний раз, когда я проверял - частично поддерживаются компиляторами Cray.

Одно явное преимущество атомарных соединений C11 - в дополнение ко всему стандартному стандарту ISO - это то, что они поддерживают более точное назначение согласованности памяти. Насколько я знаю, атомарность GCC подразумевает полный барьер памяти.

1 голос
/ 17 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...