Летучие классы в C ++ - PullRequest
       1

Летучие классы в C ++

0 голосов
/ 04 октября 2010

У меня есть вопрос относительно изменчивого ключевого слова, на который я не могу найти ответ.

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

Класс выглядит так:

class CBuffer
{
    //Constructor, destructor, Critical section initialization/destruction
    //...

    volatile wstring m_strParam;
    //...
    void InterlockedParamSetter(const wstring &strIn);
    wstring InterlockedParamGetter();

    ParamSetter(const wstring &strIn);
    wstring ParamGetter();

    Lock();
    Unlock();
}

void CBuffer::InterlockedParamSetter(const wstring &strIn)
{
    Lock();
    const_cast<wstring>(m_strParam) = strIn;
    Unlock();
}

//... other f-ns

Но компилятор жалуется на преобразование const_cast.

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

Как вы пишете потоки / кеширующие классы на C ++?

P.S .: Пока блокировка не является узким местом, и блокировки в значительной степени являются однострочными, поэтому на данный момент сериализация и блокировка не являются проблемой с точки зрения производительности. Конечно, если есть лучший алгоритм, я с удовольствием выслушаю.

EDIT: Мне все еще неясно ...

Рассмотрим этот пример (вставка + ссылка времени кода);

void Thread1Func()
{
    //Unrolled, inlined InterlockedParamSetter()
    EnterCriticalSection(&cs);
    WriteTo(CBuffer::m_strParam);//write to buffer, wstring not volatile, cache it
    LeavCriticalSection(&cs);
    //Unroll end

    //DoSomethingElse

    //!!!!Thread 2 does InterlockedParamSetter
    //which causes wstring::reserve and invalidates old pointers!!!!

    //Unrolled, inlined InterlockedParamSetter()
    EnterCriticalSection(&cs);
    WriteTo(CBuffer::m_strParam);//oh, good, we have a pointer to old buffer
    //cached in one of the registers, write to it -> access violation
    LeavCriticalSection(&cs);
    //Unroll end
}

Ответы [ 4 ]

2 голосов
/ 04 октября 2010

В переносимом коде volatile не имеет абсолютно никакого отношения к многопоточности .

В MSVC в качестве расширения , volatile -качественный простой нативныйТакие типы, как int, могут использоваться с простыми операциями чтения и хранения для атомарных доступов, но этот не распространяется на доступы чтения-изменения-записи, такие как i++, или на объекты типа класса, такие какstd::string.

Чтобы обеспечить безопасный доступ к переменной m_strParam из нескольких потоков, каждый поток должен заблокировать связанный мьютекс перед доступом к m_strParam и разблокировать его после.Блокировка / разблокировка гарантирует, что каждый поток увидит правильное значение m_strParam - не будет никакого кэширования внутреннего указателя, если другой поток изменил переменную следующей блокировкой.

Если вы используете блокировки правильно, вам не нужен квалификатор volatile, а также не нужно использовать const_cast.

2 голосов
/ 04 октября 2010

Вы должны const_cast strIn not m_strParam:

m_strParam = const_cast<wstring> (strIn);

В C ++ нет общепринятых идиом относительно безопасных потоков / кеш-классов, так как текущий распространенный стандарт C ++ ничего не говорит о параллельном программировании.volatile не дает никаких гарантий атомарности и бесполезен при написании переносимых, поточно-ориентированных программ.См. Эти ссылки:

2 голосов
/ 04 октября 2010

std::stringstd::wstring) не были разработаны, чтобы быть изменчивыми, и я бы посоветовал вам не использовать его таким образом.

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

И, как упоминает Маркус Линдблом, в механизмах блокировки обычно есть забор чтения / записи, который решает проблемы с потенциальным кэшированием.Так что вы должны быть в безопасности.

0 голосов
/ 04 октября 2010

Не нужно использовать volatile. барьер памяти компилятора должен быть достаточным.Lock / Unlock все еще необходим, хотя.То есть

class CBuffer
{
    //Constructor, destructor, Critical section initialization/destruction ...
    wstring m_strParam;
};

void CBuffer::InterlockedParamSetter(const wstring &strIn)    
{
    Lock();
    //compiler-specific memory barrier;
    m_strParam = strIn;
    //compiler-specific memory barrier;
    Unlock();
}

Хотя в некоторых компиляторах ключевое слово volatile имеет то же значение, что и барьер памяти, когда применяется к не примитивным типам, таким как wstring.Пример: VC ++

Маркировка памяти с помощью барьера памяти аналогична маркировке памяти с помощью ключевого слова volatile (C ++).Однако барьер памяти более эффективен, потому что ...

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