我有一个运行在Intel Xeon 32内核上的C ++多线程应用程序,已使用GCC 4.8.2编译并启用了优化功能。
我有多个线程(例如A,B,C)更新某些POD类型,另一个线程D每K秒读取这些变量并将其发送到GUI。线程跨多个核心和套接字生成。写操作受自旋锁保护。线程A,B,C对延迟很敏感,因此高性能是至关重要的方面。线程D对延迟不敏感。
类似的东西:
Thread A,B,C
...
// a,b,c are up to 64 bits (let's say double)
spin-lock
a = computeValue();
b = computeValue();
c = computeValue();
spin-unlock
....
Thread D
...
// a,b,c are up to 64 bits (let's say double)
currValueA = a;
currValueB = b;
currValueC = c;
sendToGui(currValueA ,currValueB ,currValueC );
....
我想利用有关保证原子操作的第8.1.1段https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html的优势,并避免放置锁来保护线程D进行的读取。
我的理解是,如果a,b,c自然对齐(其大小不超过64位),则线程D可以读取在写入过程中半途取的a,b,c值的风险。换句话说,写入和读取将自动进行。线程D将读取旧值或新值。
我的理解正确吗?
我留给编译器GCC 4.8.2来处理对齐问题,即我不使用任何gcc内置指令或函数,例如std :: alignas,sts :: alignof等。
我知道该代码不可移植。 我宁愿不要使用std :: atomic以避免任何不必要的开销。
答案 0 :(得分:0)
读取“在写入过程中途取”的值只是原子性的一个方面。
当今,处理器将值保存在特定于处理器的缓存中,因此在多处理器系统上,两个不同的处理器对于它们共享的a
可能具有不同的值。将a
标记为原子可以确保不同的处理器看到“相同”的值。
此外,编译器和处理器经常对计算进行重新排序,以便更好地利用处理工具。只要这些计算的结果没有变化,就可以了。 (这是C ++中的“好像”规则)。但是“不变”是指在单个线程内执行。当多个线程撞击同一个对象时,在单个线程中进行的优化不一定有效。而且,通常您不希望单线程代码由不执行常见优化的偏执狂编译器编译,因为它们可能会破坏多线程代码。相反,将对象标记为“原子”表示编译器应该非常谨慎地对待周围的事物,因为可以通过一些其他代码在后台更改该对象的值。
因此,您可以选择:手动滚动代码并希望它正确无误,或者接受atomic
库的编写者可能对目标系统上的原子性有更多的了解,并且会可能做得更好。