我有一个64位结构的std::array
,每个结构如下:
MyStruct
{
float _a{0};
float _b{0};
}; // Assume packed
一个线程(CPU内核)将写入64位对象,第二个线程(不同的内核)将读取它。
我正在使用英特尔x86架构,并且我知道从英特尔开发人员手册中可以保证64位写入是原子的。
但是,我担心第二个线程可能会将值缓存在寄存器中,而无法检测到何时值已更改。
volatile
关键字来告诉编译器另一个线程可能正在修改内存? 写值的线程对性能非常敏感,如果可以的话,我想避免内存障碍,互斥锁等。
无论volatile
是否在下一个C ++版本中被弃用-[volatile
从未设计或打算用于多线程]!这与Java相反,后者的volatile表示完全不同的内容(Java volatile语义与C ++原子的语义更接近)。
最好有一些有关实际问题的信息,即,有一些有关您实际要实现的目标的上下文。
根据您的描述,您只涉及两个线程-一个阅读和一个写作-我建议使用单生产者-单消费者队列。这样的队列可以仅使用两个用于头/尾索引的原子计数器来实现;值本身不必是原子的,可以是任何类型(包括不可平凡的值)。
但是要了解这是否是有效的解决方案,我需要更多信息:物料应以FIFO还是LIFO消耗?那数组呢?有多大?它是否可以溢出/下溢(即线程尝试写入/读取条目,但数组已满/为空)?应该如何处理完整/空数组?
作为C ++开发人员,您应该使用少量的盐处理低级的cpu功能。看到这个有趣的问题:'Understanding std::hardware_destructive_interference_size and std::hardware_constructive_interference_size'
高速缓存行之间出现真正的共享,从中我们可以看到上述结构应进行如下修改:
struct MyStruct
{
alignas ( hardware_constructive_interference_size ) atomic < float > _a;
alignas ( hardware_constructive_interference_size ) atomic < float > _b;
};
同时访问变量总是需要使用std :: atomic。如果您的目标对象是按顺序执行写操作,那么对于C ++而言,这对您而言并不重要。很多事情都在幕后进行,最后volatile不起作用,已被std::atomic
取代,已弃用。
MESIF协议将确保第二个线程看到写入吗?
不,这取决于操作系统,如果您的第一个线程(写线程)被确定优先级并且设法在第二个线程甚至可以读取第一个线程之前写两次,则这是数据争用并且完全依赖于操作系统。
我是否需要volatile关键字来告诉编译器另一个线程可能正在修改内存?
volatile
告诉编译器不要优化变量away,不要完全不优化变量。
我需要原子吗?
取决于,您不打算使用互斥体,您不打算使用任何与远程并发相关的东西,除了原子以外,原子更适合跨线程writing。
我建议将std :: mutex与std::lock_guard
或std::scoped_lock
结合使用。
我知道您的标题说您不想要它,但这实际上是保证每次阅读和写作的顺序相同的唯一方法。
atomics-一旦有两个线程在一起交谈,您将需要原子操作来读写。
std :: atomic whatEver;
[如果幸运的话,您会得到
bool is_lock_free = whatEver.is_lock_free();
如果有的话,更幸运
bool is_lock_free = whatEver.is_always_lock_free();
您将不得不做
auto whatEver = arrayOfWhatEver[x]; // atomic
auto a = whatEver._a;
使用原子操作,而不仅仅是读取MyStruct的单个成员。