C++ 0x атомарная реализация в C++ 98 вопросов о __ sync_synchronize ()

Я записал следующий атомарный шаблон в целях подражания атомарным операциям, которые будут доступны в предстоящем 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 );
    }

};

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

7
задан ScaryAardvark 11 March 2010 в 13:07
поделиться

2 ответа

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();

вместо этого. Потому что вы хотите быть уверены, что значение записано, как только вы вернете из функции.

-1
ответ дан 7 December 2019 в 14:31
поделиться

Во-первых, несколько мелких замечаний:

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 позволяет указать точные ограничения порядка памяти.

5
ответ дан 7 December 2019 в 14:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: