Я записал следующий атомарный шаблон в целях подражания атомарным операциям, которые будут доступны в предстоящем C++ 0x стандарт.
Однако я не уверен, что __ sync_synchronize () звонят, я имею вокруг возврата базового значения, необходимы.
От моего понимания, __ sync_synchronize () полный барьер памяти, и я не уверен, что мне нужен такой дорогостоящий вызов при возвращении объектного значения.
Я вполне уверен, это будет необходимо вокруг установки значения, но я мог также реализовать это с блоком..
__asm__ __volatile__ ( "rep;nop": : :"memory" );
Делает любой знает, нужно ли мне определенно синхронизирование () по возврату объекта.
M.
template < typename T >
struct atomic
{
private:
volatile T obj;
public:
atomic( const T & t ) :
obj( t )
{
}
inline operator T()
{
__sync_synchronize(); // Not sure this is overkill
return obj;
}
inline atomic< T > & operator=( T val )
{
__sync_synchronize(); // Not sure if this is overkill
obj = val;
return *this;
}
inline T operator++()
{
return __sync_add_and_fetch( &obj, (T)1 );
}
inline T operator++( int )
{
return __sync_fetch_and_add( &obj, (T)1 );
}
inline T operator+=( T val )
{
return __sync_add_and_fetch( &obj, val );
}
inline T operator--()
{
return __sync_sub_and_fetch( &obj, (T)1 );
}
inline T operator--( int )
{
return __sync_fetch_and_sub( &obj, (T)1 );
}
inline T operator-=( T )
{
return __sync_sub_and_fetch( &obj, val );
}
// Perform an atomic CAS operation
// returning the value before the operation
inline T exchange( T oldVal, T newVal )
{
return __sync_val_compare_and_swap( &obj, oldval, newval );
}
};
Обновление: Я хочу удостовериться, что операции последовательны перед лицом чтения-записи, переупорядочивающего из-за оптимизаций компилятора.
inline operator T()
{
__sync_synchronize(); // Not sure this is overkill
return obj;
}
Короткая версия: Это излишество.
Длинная версия:
Почему вы вообще хотите реализовать этот класс в качестве шаблона? Это не имеет смысла, потому что атомарные операции разрешены только для целочисленных типов от 1 до 8 байт, и вы даже не можете быть уверены, что 8-байтовое целое число поддерживается на всех платформах.
Вы должны реализовать свой атомарный класс как версию, не восписную и использовать "родной" целочисленный тип вашего оборудования/системы. Это int32_t на 32-разрядных процессорах / ОС и int64_t на 64-разрядных системах. Например:
#ifdef ...
typedef ... native_int_type;
#endif
// verify that you choosed the correct integer type
BOOST_STATIC_ASSERT(sizeof(native_int_type) == sizeof(void*));
BOOST_STATIC_ASSERT прямо к "static_assert()" из C++0x.
Если вы используете целочисленный тип "perfect fit", вы можете записать оператор просто так:
operator native_int_type() { return obj; }
Поскольку obj является переменным, он гарантированно извлекет значение и не возвращает кэшированное значение. И поскольку вы используете «родной» целочисленный тип, вы можете быть уверены, что чтение такого значения является атомарным.
atomic& operator=( native_integer_type val )
Опять же, синхронизация не нужна, если вы используете правильный целочисленный тип.Чтение/установка int32 в 32-битной системе intel является атомарной, как и чтение/установка int64 в 64-битной системе.
Я не вижу никакой пользы от реализации atomic в качестве шаблона. Атомарные операции зависят от платформы. Лучше предложить класс «atomic_int», который просто гарантирует, что имеет не менее 4 байт (если вы поддерживаете 32-битные и 64-битные системы) и «atomic_pointer», если вам это нужно. Таким образом, имя класса также подразумевает семантику и цель.
Если вы просто используете «atomic», то можно подумать: «Вау, мне просто нужно поместить свой строковый класс в этот шаблон, и тогда он будет безопасным для потоков!».
Правка: Чтобы ответить на ваше обновление: «Я хочу убедиться, что операции согласованы перед лицом переупорядочивания чтения/записи из-за оптимизации компилятора».
Чтобы компилятор и процессор не переупорядочены операциями чтения/записи, необходим _sync_synchronize().
Но обратите внимание, что семантика приобретения/выпуска может дать лучшую производительность, чем полные барьеры.
Правка2:
inline atomic< T > & operator=( T val )
{
__sync_synchronize(); // Not sure if this is overkill
obj = val;
return *this;
}
Что вы хотите предотвратить от повторного заказаряда? В большинстве случаев вы хотите написать это:
obj = val;
__sync_synchronize();
вместо этого. Потому что вы хотите быть уверены, что значение записано, как только вы вернете из функции.
volatile T obj;
volatile здесь бесполезен, тем более что вы сами делаете все преграды.
inline T operator++( int )
inline не требуется, поскольку это подразумевается, когда метод определяется внутри класса.
inline operator T()
{
__sync_synchronize(); // (I)
T tmp=obj;
__sync_synchronize(); // (II)
return tmp;
}
inline atomic< T > & operator=( T val )
{
__sync_synchronize(); // (III)
obj = val;
__sync_synchronize(); // (IV)
return *this;
}
Чтобы обеспечить полное упорядочение доступа к памяти при чтении и записи, вам нужно два барьера для каждого доступа (как это). Я был бы доволен только барьерами (II) и (III), поскольку они достаточны для некоторых применений, которые я придумал (например, указатель / логическое значение, говорящее, что данные есть, спин-блокировка), но, если не указано иное, я бы не пропустил другие , потому что кому-то они могут понадобиться (было бы неплохо, если бы кто-то показал, что вы можете опустить некоторые из барьеров, не ограничивая возможное использование, но я не думаю, что это возможно).
Конечно, это будет излишне сложно и медленно.
Тем не менее, я бы просто сбросил барьеры и даже идею использования барьеров в любом месте аналогичного шаблона. Обратите внимание:
Кстати, интерфейс c ++ 0x позволяет указать точные ограничения порядка памяти.