如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
我有两个线程,一个更新一个int,另一个读取它.这是一个统计值,其中读取和写入的顺序无关紧要.
我的问题是,我是否需要同步访问这个多字节值?或者,换句话说,写入的一部分可以完成并被中断,然后读取就会发生.
例如,假设值= 0x0000FFFF,其值递增为0x00010000.
是否有时间值看起来像0x0001FFFF,我应该担心?当然,类型越大,发生这种情况的可能性就越大.
我总是同步这些类型的访问,但很好奇社区的想法.
C++ 17的当前标准(我已经观察到类似于C++ 11的措辞)对于简单的可复制类型具有非常混乱的措辞.我首先使用以下代码(GCC 5.3.0)偶然发现了这个问题:
class TrivialClass {};
std::is_trivially_copyable<int volatile>::value; // 0
std::is_trivially_copyable<TrivialClass volatile>::value; // 1 ??
Run Code Online (Sandbox Code Playgroud)
让混乱更加糟糕,我试着检查std::is_trivial一下这件事情有什么说法,只是让人更加困惑.
class TrivialClass {};
std::is_trivial<int volatile>::value; // 1 ??
std::is_trivial<TrivialClass volatile>::value; // 1
Run Code Online (Sandbox Code Playgroud)
很困惑,我检查了最新的C++ 17草案,看看是不是有问题,我发现了一些有些含糊不清的措辞可能是罪魁祸首:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.73
cv-非限定标量类型,平凡可复制类类型(第9节),此类类型的数组以及这些类型的非易失性const限定版本(3.9.3)统称为平凡可复制类型.
以下是关于平凡可复制类的信息:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#page.226
一个简单的可复制类是一个类:
- (6.1)没有非平凡的复制构造函数(12.8),
- (6.2)没有非平凡的移动构造函数(12.8),
- (6.3)没有非平凡的副本分配操作员(13.5.3,12.8),
- (6.4)没有非平凡的移动指派算子(13.5.3,12.8),和
- (6.5)有一个简单的析构函数(12.4).
http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4567.pdf#section.12.8
构造函数:
如果不是用户提供的,则类X的复制/移动构造函数是微不足道的,其参数类型列表等效于隐式声明的参数类型列表,如果
- (12.1)类X没有虚函数(10.3),没有虚基类(10.1),和
- (12.2)类X 没有volatile限定类型的非静态数据成员,和
- (12.3)选择复制/移动每个直接基类子对象的构造函数是微不足道的,并且
- (12.4)对于类型(或其数组)的X的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;
否则复制/移动构造函数是非平凡的.
分配:
如果类X不是用户提供的,则类X的复制/移动赋值运算符是微不足道的,其参数类型列表等效于隐式声明的参数类型列表,如果
- (25.1)类X没有虚函数(10.3),没有虚基类(10.1),和
- (25.2)类X 没有volatile限定类型的非静态数据成员,和
- (25.3)选择复制/移动每个直接基类子对象的赋值运算符是微不足道的,并且
- (25.4)对于类型(或其数组)的X的每个非静态数据成员,选择复制/移动该成员的赋值运算符是微不足道的;
否则复制/移动赋值运算符是非常重要的.
注意:更新了此部分以及更多信息.我现在相信这是GCC中的一个错误.然而,仅凭这一点并不能回答我的所有问题.
我可以看到,也许是因为TrivialClass没有非静态成员,因为它会传递上述规则,所以我添加了一个int,它仍然可以简单地复制.
class TrivialClass { …Run Code Online (Sandbox Code Playgroud) 我想验证我的理解是否正确.这种事情很棘手,所以我几乎可以肯定我错过了什么.我有一个由实时线程和非实时线程组成的程序.我希望非RT线程能够将指针交换到RT线程使用的内存.
从文档中,我的理解是,这可以通过以下方式实现g++:
// global
Data *rt_data;
Data *swap_data(Data *new_data)
{
#ifdef __GNUC__
// Atomic pointer swap.
Data *old_d = __sync_lock_test_and_set(&rt_data, new_data);
#else
// Non-atomic, cross your fingers.
Data *old_d = rt_data;
rt_data = new_data;
#endif
return old_d;
}
Run Code Online (Sandbox Code Playgroud)
这是程序中唯一rt_data被修改的地方(初始设置除外).当rt_data在实时上下文中使用,它被复制到本地指针.对于old_d以后,当确定未使用旧内存时,它将在非RT线程中释放.它是否正确?我需要volatile在任何地方吗?我应该调用其他同步原语吗?
顺便说一下,我在C++中这样做,虽然我对C的答案是否不同感兴趣
提前谢谢.
什么时候可以保证64位写入是原子的,在基于Intel x86的平台上用C编程时(特别是使用英特尔编译器运行MacOSX 10.4的基于Intel的Mac)?例如:
unsigned long long int y;
y = 0xfedcba87654321ULL;
/* ... a bunch of other time-consuming stuff happens... */
y = 0x12345678abcdefULL;
Run Code Online (Sandbox Code Playgroud)
如果另一个线程在y的第一次赋值完成后检查y的值,我想确保它看到值0xfedcba87654321或值0x12345678abcdef,而不是它们的某些混合.我想这样做没有任何锁定,如果可能的话没有任何额外的代码.我希望在使用64位编译器(64位Intel编译器)时,在能够支持64位代码的操作系统(MacOSX 10.4)上,这些64位写入将是原子的.这总是如此吗?
我在许多参考文献中发现它提到volatile在C/C++中很弱并且可能在多处理器的并发环境中引起问题,但它(volatile)可以用作C#/ Java中差异CPU之间的通信机制.看起来这个关键字在C#/ Java中比在C/C++中更严格,但它们之间的差异/影响是什么?
这是volatileC/C++ 的参考.
为什么volatile在多线程C或C++编程中不被认为有用?
看着经过一大堆 的 其他 问题 和 他们的 答案,我得到的印象是有什么在C“挥发性”关键字表示正好没有广泛的协议。
即使标准本身似乎也不够清晰,以至于每个人都无法理解其含义。
除其他问题外:
总结一下问题,似乎(经过大量阅读)“ volatile”保证了类似的结果:该值将不但从/向寄存器,而且至少向内核的L1缓存中读/写,其顺序与读/写出现在代码中。但这似乎没有用,因为在同一线程内从寄存器中读取/写入寄存器已经足够,而与L1缓存进行协调并不能保证与其他线程进行协调。我无法想象仅与L1缓存进行同步的重要性。
用途1
唯一广泛同意使用volatile的似乎是旧的或嵌入式系统,其中某些内存位置通过硬件映射到I / O功能,例如内存中的某个位(直接在硬件中)控制灯光。 ,或内存中的某个位告诉您键盘键是否按下(因为它是通过硬件直接连接到键的)。
看来,“用1”不移植的代码,其目标包括多核系统发生。
USE 2
与“ use 1”没什么不同,是可由中断处理程序(可以控制灯光或存储来自按键的信息)随时读取或写入的内存。但是为此已经存在一个问题,即取决于系统,中断处理程序可能会在 具有自己的内存缓存的不同内核上运行,并且“ volatile”不能保证所有系统上的缓存一致性。
因此,“使用2”似乎超出了“易失性”所能提供的范围。
用途3
我看到的唯一其他无可争议的用途是防止通过指向编译器未意识到的同一内存的不同变量的不同变量对访问进行错误优化。但这可能只是无可争议的,因为人们没有在谈论它-我只看到其中一个提及。而且我认为C标准已经认识到“不同”的指针(例如指向函数的不同args)可能指向同一项目或附近的项目,并且已经指定编译器必须生成即使在这种情况下也可以工作的代码。但是,我无法在最新的标准(500页!)中快速找到此主题。
那么“使用3”也许根本不存在?
因此,我的问题是:
在多核系统的可移植C代码中,“ volatile”是否可以保证任何东西?
浏览最新标准后,答案似乎至少是非常有限的:
1.该标准针对特定类型“ volatile sig_atomic_t”反复指定特殊处理。但是该标准还说,在多线程程序中使用信号功能会导致不确定的行为。因此,该用例似乎仅限于单线程程序与其信号处理程序之间的通信。
2.该标准还为setjmp / longjmp指定了“ volatile”的明确含义。(在其他问题和答案中给出了重要示例代码)。
因此,更精确的问题变成了:
除了(1)允许单线程程序从其信号处理程序接收信息之外,还是(2)允许setjmp,“ volatile”对于多核系统的便携式C代码是否有任何保证?代码以查看在setjmp和longjmp之间修改的变量?
这仍然是一个是/否问题。
如果为“是”,那么最好显示一个无错误的可移植代码示例,如果省略了“ volatile”,则该示例会出现错误。如果为“ no”,那么我认为对于多核目标,在这两种非常特殊的情况下,编译器可以随意忽略“ volatile”。
请考虑以下示例.目标是使用两个线程,一个用于"计算"一个值,另一个用于消耗和使用计算值(我试图简化这个).计算线程通过使用条件变量向另一个线程发信号通知该值已经计算并准备就绪,之后等待线程消耗该值.
// Hopefully this is free from errors, if not, please point them out so I can fix
// them and we can focus on the main question
#include <pthread.h>
#include <stdio.h>
// The data passed to each thread. These could just be global variables.
typedef struct ThreadData
{
pthread_mutex_t mutex;
pthread_cond_t cond;
int spaceHit;
} ThreadData;
// The "computing" thread... just asks you to press space and checks if you did or not
void* getValue(void* td)
{
ThreadData* data …Run Code Online (Sandbox Code Playgroud) 我最近在C++多线程代码中遇到了volatile关键字的奇怪用法.为了抽象编程模式,我们假设有一个控制对象可以被一个生产者和几个消费者线程访问:
class control_t {
pthread_mutex_t control_lock;
pthread_cond_t wake_cond;
bool there_is_work_todo;
control_t volatile* vthis() { return this; }
}
Run Code Online (Sandbox Code Playgroud)
消费者线程执行以下操作(c是指向控件对象的非易失性指针):
...
pthread_mutex_lock(c->control_lock)
while (!c->vthis()->there_is_work_todo) {
pthread_cond_wait(&c->wake_cond, &c->control_lock);
}
...
Run Code Online (Sandbox Code Playgroud)
这里的想法是消费者线程将等待,直到有一些工作要做,生产者通过wake_cond变量发出信号.
我在这里不明白的是为什么控制对象是通过指向"this"的易失性指针访问的,该指针由方法vthis()返回.这是为什么?
我读过的关于volatile的一切都说它永远不会安全,但我仍然倾向于尝试它,而且我还没有看到这个特定场景被宣布为不安全.
我有一个单独的线程渲染场景,从主模拟线程中提取数据.这没有同步,并且工作正常.
问题是当程序退出时,渲染器需要停止从模拟线程中提取数据,然后模拟线程才能安全地自我清理,而不会导致渲染器尝试读取无效内存.
为了实现这一点,我让渲染器在其线程中无限运行:
volatile bool stillRendering;
void RenderThreadFunction()
{
stillRendering = true;
while(programRunning)
{
renderer->render();
}
stillRendering = false;
}
Run Code Online (Sandbox Code Playgroud)
在主程序线程中,当收到windproc退出消息时,我会:
void OnQuit()
{
programRunning = false;
while(stillRendering)
{
}
delete application;
}
Run Code Online (Sandbox Code Playgroud)
这样做的目的是确保渲染器在应用程序上调用delete之前停止从应用程序中提取数据.
我首先尝试了这个没有任何volatile关键字,并且它在调试模式下工作,但在发布模式下它挂了.我假设编译器进行了一些优化,导致程序停止检查stillRendering的值.
将volatile添加到stillRendering会导致应用程序在我到目前为止每次测试时成功退出.我不确定为什么"programRunning"不稳定似乎并不重要.
最后,我不确定如何使用volatile为"stillRendering"影响程序的性能.如果使用StillRendering volatile会影响OnQuit()的性能,那对我来说并不重要,但如果它影响RenderThreadFunction()的性能,它对我来说很重要
c++ ×8
c ×4
volatile ×4
atomic ×3
pthreads ×2
atomic-swap ×1
boolean ×1
c# ×1
c++11 ×1
concurrency ×1
g++ ×1
java ×1
lock-free ×1
macos ×1
portability ×1
standards ×1
type-traits ×1