C++ 多线程和运算符++;关于基元类型

C++ 多线程和运算符++;关于基元类型,c++,multithreading,volatile,C++,Multithreading,Volatile,因此,我在这里和其他地方读了一大堆关于共享变量、多线程和volatile的文章 如果您考虑以下代码: class C { int x; public: C() : x(0) { } void Operation() { AcquireMutex(); ++x; ReleaseMutex(); } }; 现在,如果我已经理解了到目前为止阅读的所有内容,这将是更新x的正确方法,对吗?正确的编译器不会在调用Acqui

因此,我在这里和其他地方读了一大堆关于共享变量、多线程和volatile的文章

如果您考虑以下代码:

class C {
    int x;

public:
    C() : x(0) { }

    void Operation() {
        AcquireMutex();
        ++x;
        ReleaseMutex();
    }
};
现在,如果我已经理解了到目前为止阅读的所有内容,这将是更新
x
的正确方法,对吗?正确的编译器不会在调用
AcquireMutex()
之前对代码重新排序,以缓存
x
的值,对吗

我一直有一个习惯,就是用
volatile
标记这些变量。这是我在学校学到的东西,早在恐龙在陆地上游荡的时候,我就没有真正思考过。在阅读了关于这个主题的文章之后,我似乎浪费了几分钟的时间来输入一个(对于这些类型的使用)无用的关键字

更新: 好的,如果我将
Operation()
改为:

void Operation() {
    AcquireMutex();
    ++x;
    ReleaseMutex();
    AcquireMutex();
    ++x;
    ReleaseMutex();
}
现在,让我们忽略互斥体的使用,以及诸如InterlockedIncrement()之类的内部函数。这有点不合我的意思


如果
x
未标记为
volatile
,则上述代码是否是线程安全的?是否编译器决定在第一次增量后将
x
的最后一个值保存在寄存器中,然后只增加寄存器的值,并在最后一次增量时将其存储在内存中?如果是这种情况,那么上面的代码就不是线程安全的。有什么好处?编译器是否会假设在调用任何函数后,所有缓存的变量都被认为是“脏的”,从而迫使编译器发出读取操作?

如果您在Windows上使用Visual Studio,您可以尝试使用所谓的内部函数:

#include <intrin.h>
class C {
    int x;

public:
    C() : x(0) { }

    void Operation() {
        _InterlockedIncrement(&x);
    }
};
#包括
C类{
int x;
公众:
C():x(0){}
无效操作(){
_联锁增量(&x);
}
};

我不知道其他操作系统,但我相信也有intrinsic。

如果您在Windows上使用Visual Studio,您可以尝试使用所谓的intrinsic:

#include <intrin.h>
class C {
    int x;

public:
    C() : x(0) { }

    void Operation() {
        _InterlockedIncrement(&x);
    }
};
#包括
C类{
int x;
公众:
C():x(0){}
无效操作(){
_联锁增量(&x);
}
};

我不知道其他操作系统,但我确信也有内在的。

我不太确定Martin是对的。看看这个:

如果32位递增是原子的,为什么需要
InterlocatedCremenet


也就是说,你不应该对这种东西使用互斥,这是一种巨大的浪费。使用CPU内部函数,如win32 api中的
联锁*
函数(以及其他编译器库中的等效函数)。

我不太确定Martin是否正确。看看这个:

如果32位递增是原子的,为什么需要
InterlocatedCremenet


也就是说,你不应该对这种东西使用互斥,这是一种巨大的浪费。使用CPU内部函数,如win32 api中的
联锁*
函数(以及其他编译器库中的等效函数)。

volatile
不涉及原子性。其目的是防止缓存不应缓存的内存位置(例如,硬件设备DMA端口)(编辑:此措辞指的是“缓存”通过生成的代码。例如,非易失性变量可能从内存中读取,然后无限期地保存在寄存器中。Arkadiy在下面的注释中提供了更精确的定义。)


和其他人注意到的,C或C++中的任何操作都不能保证是原子的。您可以根据需要自行管理互斥锁或其他防护装置。

volatile
没有提到原子性。其目的是防止缓存不应缓存的内存位置(例如,硬件设备DMA端口)(编辑:此措辞指的是“缓存”通过生成的代码。例如,非易失性变量可能从内存中读取,然后无限期地保存在寄存器中。Arkadiy在下面的注释中提供了更精确的定义。)


和其他人注意到的,C或C++中的任何操作都不能保证是原子的。您可以根据需要自行管理互斥体或其他防护装置。

更准确地说,“volatile”告诉编译器,此位置的值可以在编译代码之外更改,因此任何依赖于相同值的优化都无法应用。对
sig_atomic_t
变量的操作都保证是原子的,至少在信号方面。它(不一定)与线程有关,但只是为了防止有人觉得“我肯定我在C中看到了一些关于原子类型的东西。”@Jerry,
sig_atomic_t
提供了对特定大小整数变量的原子访问——对这些变量的单个读写不能被信号中断。它不能保证对这些变量的操作是原子的。例如,
++
操作表示两种访问——读和写。它不能保证所有操作都是原子操作,但我很难相信有人会诚实地说读和写都不是操作…@Jerry:我想在前面的评论中提供这个链接:。这不是权威,但我认为它准确地描述了情况。具体地说,它表明
sig_atomic_t
提供了变量的原子读写;它不提供原子操作,这需要读和写。更准确地说,“volatile”告诉编译器,此位置的值可以在编译代码之外更改,因此任何依赖于相同值的优化都不能应用。对
sig_atomic_t
变量的操作保证是原子的,至少在信号方面。它(不一定)与线程有关,但以防有人觉得“我肯定我在C中看到过原子类型的东西”。@Jerry,