2013-05-15 2 views
1

Я пытаюсь написать класс-оболочку для обертки внутренней функции Win32, такой как InterlockedIncrement, InterlockedExchange. Хотя моя проблема, вероятно, аналогична и на других платформах, которые поддерживают подобные функции.Как можно написать безопасную оболочку атомного объекта?

У меня есть базовый тип шаблона:

template <typename T, size_t W = sizeof(T)> 
class Interlocked {}; 

который частично специализированы для типов данных разного размера. Например, вот 32 бит один:

// 
// Partial specialization for 32 bit types 
// 
template<typename T> 
class Interlocked <T, sizeof(__int32)> 
{ 
public: 

    Interlocked<T, sizeof(__int32)>() {}; 

    Interlocked<T, sizeof(__int32)>(T val) : m_val(val) {} 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator= (T val) 
    { 
     InterlockedExchange((LONG volatile *)&m_val, (LONG)val); 
     return *this; 
    } 

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator++() 
    { 
     return static_cast<T>(InterlockedIncrement((LONG volatile *)&m_val)); 
    } 

    Interlocked<T, sizeof(__int32)> Interlocked<T, sizeof(__int32)>::operator--() 
    { 
     return static_cast<T>(InterlockedDecrement((LONG volatile *)&m_val)); 
    } 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator+(T val) 
    { 
     InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val); 
     return *this; 
    } 

    Interlocked<T, sizeof(__int32)>& Interlocked<T, sizeof(__int32)>::operator-(T val) 
    { 
     InterlockedExchangeSubtract((LONG volatile *)&m_val, (LONG) val); 
     return *this; 
    } 

    operator T() 
    { 
     return m_val; 
    } 

private: 

    T m_val; 
}; 

Однако, я прихожу к выводу, что я не знаю, как безопасно писать такой объект. В частности, я понял, что возвращение *this после выполнения операции блокировки позволяет использовать другой поток для изменения переменной до ее возврата. Это аннулирует точку типа. Можно ли написать такое? Предположительно станд :: атомными решает эту проблему, но у меня нет доступа к, что в моем компиляторе ...

ответ

7

Если у вас нет std::atomic, вы можете использовать boost::atomic (появился в последней Boost 1.53), который хорошо проверенная кросс-платформенная реализация.

+0

Спасибо, что я не знал об «boost :: atomic». Хотя мне все же хотелось бы знать, как это возможно в теории. – Benj

+0

@Benj: Хорошо, вижу. Вы можете посмотреть, как это реализовано в Boost. – nogard

2

Операторы + и - не имеют смысла. То, что вы на самом деле реализовали, больше похоже на составное присвоение (+=, -=), но вам нужно вернуть значение типа T, а не ссылку на (*this). Конечно, это не соответствует соглашениям для операторов присваивания ... std::atomic предпочитает использовать именованные функции, а не перегрузки операторов для всего, кроме ++ и --, возможно по этой причине.

0

Помимо очень хорошего совета «использовать чужой уже протестированный и рабочий процесс» из nogard, я бы предположил, что вы не хотите возвращать *this, но результат операции - это то, как существующие блокированные операторы работают (и как работает std :: atomic).

Итак, другими словами, ваш код оператора должен выглядеть следующим образом:

T Interlocked<T, sizeof(__int32)>::operator+(T val) 
{ 
    return InterlockedExchangeAdd((LONG volatile *)&m_val, (LONG) val); 
} 

Существует проблема, как и Бен Voigt poinst, что, что эта функция изменяет значение входного сигнала, что означает, что:

a = b + c; 

бы на самом деле:

b += c; 
a = b; 
+0

. Я попытался это сделать, но, конечно, если вы не вернете * это из назначения (например), то ваш объект не будет вести себя как обычный тип. – Benj

+0

Я сомневаюсь, что существует разумный способ, которым вы могли бы использовать что-то вроде 'Interlocked a, b, c; ... a = b + c; 'и полагаться на результат каким-либо значимым образом. –

+0

@Benj: Атомный тип * не является * нормальным типом, так почему он должен действовать как один? Целью обертки должно быть упрощение использования, а не абстрагирование ее атомной природы. –

0

Рассмотрим две темы, исполнительское конц текущие дополнения в вашем классе атомных чисел, где Thread #n добавляет сумму t_n к вашему номеру x.

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

Наблюдаемое поведение для пользователя класса заключается в том, что возвращаемое значение равно (x + t_1 + t_2) вместо ожидаемого (x + t_1).

Теперь предположим, что у вас была реализация, которая не допустила бы такого поведения, т. Е.результат гарантированно будет (x_1 + t_1), где x_1 - это значение числа непосредственно перед тем, как Thread # 1 выполняет его добавление.

Если Thread # 2 выполняет свое параллельное сложение непосредственно перед Thread # 1 значение, которое вы получаете:

(x_1 + t_1) = ((x + t_2) + t_1) 

Какой точно такой же гонки. Если вы не вводите дополнительную синхронизацию или проверку ожидаемого значения номера перед применением добавления, вы всегда будете получать эту гонку.

1

У вас есть гонки данных в коде

Вы можете одновременно записать в переменную (используя InterlockedBlah (...)) и читать из него с помощью оператора Т.

модель памяти для C + +11 утверждает, что это недопустимо. Вы можете полагаться на спецификации оборудования для своей платформы, которые могут утверждать, что 4 байта (выровненные!) Чтения не разрываются, но в лучшем случае это хрупкое. И неопределенное поведение не определено.

Кроме того, считывание не имеет каких-либо барьеров памяти [которые говорят как компилятору, так и аппаратным средствам] не изменять порядок инструкций.

Выполнение считывания return InterlockedAdd (& val, 0) операция, вероятно, решит все эти проблемы, так как блокированные API-интерфейсы на окнах гарантируют добавление правильных барьеров памяти. Однако будьте осторожны с Interlocked * API на других платформах MS, у которых нет этой гарантии.

В принципе, вы пытаетесь сделать это, вероятно, возможно, но очень сложно, и определенно полагаетесь на то, что программное обеспечение и аппаратные гарантии гарантируют на каждой платформе - это невозможно написать это портативным способом.

Использование std :: atomic, use boost :: atomic

Смежные вопросы