C++ 中的易失性类

发布于 2024-09-25 16:10:45 字数 1621 浏览 1 评论 0原文

我有一个关于 volatile 关键字的问题,我似乎找不到答案。

在我的应用程序中,我有作为线程之间的状态缓冲区共享的数据类,并且我需要从多个线程定期更新它。

类看起来像这样:

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++ 编写线程/缓存安全类?

PS:到目前为止,锁定还不是瓶颈,而且锁定几乎都是单行的,因此从性能角度来看,序列化和锁定目前不是问题。当然,如果有更好的算法,我很乐意倾听。

编辑: 我仍然不清楚......

考虑这个例子(内联+链接时间代码生成);

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
}

I have a question concerning volatile keyword I can't seem to find an answer for.

In my app I have data class that is shared as a state buffer between threads, and I need it to be updated regularly from multiple threads.

Class looks like this:

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

But the compiler complains about const_cast conversion.

It almost looks like I'm abusing volatile keyword, but at the same time, I can't let the params to be cached between the calls, because if two or three threads will assign them, something can go wrong.

How do you write thread/cache safe classes in C++?

P.S.: So far locking is not the bottleneck, and the locks are pretty much single-liners, so for now serialization and locking is not an issue from performance standpoint. Of course, if there is a better algorithm, I will gladly listen.

EDIT:
I'm still unclear...

Consider this example (inlining + link time codegen);

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

葬心 2024-10-02 16:10:45

在可移植代码中,易失性与多线程完全无关

在 MSVC 中,作为扩展易失性限定的简单本机类型(例如 int)可以与简单的读取和存储操作一起用于原子访问,但这扩展到读-修改-写访问,例如i++或类类型的对象,例如std::string

为了确保从多个线程安全访问 m_strParam 变量,每个线程必须在访问 m_strParam 之前锁定关联的互斥锁,然后再解锁。锁定/解锁将确保每个线程都能看到 m_strParam 的正确值——如果另一个线程通过下一个锁定修改了该变量,则不会对内部指针进行任何缓存。

如果正确使用锁,则不需要 volatile 限定符,也不需要使用 const_cast

In portable code, volatile has absolutely nothing to do with multithreading.

In MSVC, as an extension, volatile-qualified simple native types such as int can be used with simple read and store operations for atomic accesses, but this does not extend to read-modify-write accesses such as i++ or to objects of class type such as std::string.

To ensure safe access to your m_strParam variable from multiple threads, each thread must lock the associated mutex before accessing m_strParam, and unlock it afterwards. The lock/unlock will ensure that the correct value of m_strParam is seen by each thread --- there will not be any caching of the internal pointer if another thread has modified the variable by the next lock.

If you use locks correctly, you do not need the volatile qualifier, nor do you need to use const_cast.

没有你我更好 2024-10-02 16:10:45

您应该 const_cast strIn 而不是 m_strParam:

m_strParam = const_cast<wstring> (strIn);

C++ 中没有关于线程/缓存安全类的普遍接受的习惯用法,因为当前流行的 C++ 标准对并发编程保持沉默。 易失性不保证任何原子性,并且在编写可移植的、线程安全的程序时毫无用处。请参阅以下链接:

You should const_cast strIn not m_strParam:

m_strParam = const_cast<wstring> (strIn);

There are no generally accepted idioms about thread/cache safe classes in C++, as the current prevalent C++ standard is silent about concurrent programming. volatile does not make any guarantees of atomicity and is useless in writing portable, thread-safe programs. See these links:

口干舌燥 2024-10-02 16:10:45

std::string (和 std::wstring)并不是设计为易失性的,我建议您不要以这种方式使用它。

确保线程安全的常用方法是在需要时使用互斥体、信号量并避免使用全局变量。

正如 Marcus Lindblom 提到的,锁定机制中通常有读/写栅栏,负责处理潜在的缓存问题。所以你应该是安全的。

std::string (and std::wstring) wasn't designed to be volatile and I would advise you against using it that way.

The usual way to ensure thread-safety is to use mutexes, semaphores, where needed and to avoid the use of global variables.

And as Marcus Lindblom mention, there are usually read/write fence in the lock mechanisms that take care of dealing with potential caching issues. So you should be safe.

天涯离梦残月幽梦 2024-10-02 16:10:45

无需使用易失性编译器内存屏障应该足够了。不过,仍然需要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();
}

尽管在某些编译器上,当应用于诸如 wstring 之类的非基本类型时,volatile 关键字与内存屏障具有相同的含义。恰当的例子:VC++

用内存屏障标记内存
类似于用
易失性 (C++) 关键字。然而,一个
内存屏障更有效
因为...

No need to use volatile. A compiler memory barrier should be sufficient. The Lock/Unlock is still needed, though. That is,

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

Although on some compilers, the volatile keyword has the same meaning as memory barrier when applied to non-primitive types like wstring. Case in point: VC++

Marking memory with a memory barrier
is similar to marking memory with the
volatile (C++) keyword. However, a
memory barrier is more efficient
because ...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文