- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我知道在C++中赋值可能不是原子的。
我试图触发一个竞赛条件来显示这一点。
但是,我的以下代码似乎没有触发任何此类事件。
我如何更改它以使其最终触发比赛条件?
#include <iostream>
#include <thread>
volatile uint64_t sharedValue = 1;
const uint64_t value1 = 13;
const uint64_t value2 = 1414;
void write() {
bool even = true;
for (;;) {
uint64_t value;
if (even)
value = value1;
else
value = value2;
sharedValue = value;
even = !even;
}
}
void read() {
for (;;) {
uint64_t value = sharedValue;
if (value != value1 && value != value2) {
std::cout << "Race condition! Value: " << value << std::endl << std::flush;
}
}
}
int main()
{
std::thread t1(write);
std::thread t2(read);
t1.join();
}
sharedValue = value;
00D54AF2 mov eax,dword ptr [ebp-18h]
00D54AF5 mov dword ptr [sharedValue (0D5F000h)],eax
00D54AFA mov ecx,dword ptr [ebp-14h]
00D54AFD mov dword ptr ds:[0D5F004h],ecx
sharedValue
中?
uint32_t
,一次就复制了所有数据。
std::atomic
?
最佳答案
一些答案/评论建议在作家中 sleep 。这是没有用的。您想要的是尽可能快地更改高速缓存行。 (以及volatile
分配和读取的结果。)当对高速缓存行的MESI共享请求到达写入器核心(介于将存储的两半从存储缓冲区提交到L1d高速缓存之间)时,分配将被破坏。
如果您 sleep 了,那么您将等待很长时间,而没有创建一个窗口来实现此目的。在两半之间 sleep 会使检测起来更加容易,但是除非您使用单独的memcpy
编写64位整数等的两半,否则您将无法做到这一点。
即使写入是原子的,也可以在读取器中的两次读取之间进行撕裂。这可能不太可能,但是在实践中仍然会发生很多。现代的x86 CPU可以在每个时钟周期执行两次加载(自Sandybridge以来为Intel,自K8以来为AMD)。我使用原子64位存储进行了测试,但是在Skylake上拆分了32位负载,并且撕裂仍然很频繁,足以在终端机中弹出几行文本。因此,CPU不能以总是在同一时钟周期内执行相应读取对的方式,以锁步方式运行所有内容。因此,有一个窗口供读取器使一对缓存之间的缓存行无效。 (但是,当缓存行由写入器核心拥有时,所有未决的缓存未命中加载可能在缓存行到达时立即全部完成。可用的加载缓冲区总数在现有的微体系结构中为偶数。)
如您所见,您的测试值都具有0
的上半部分,因此无法观察到任何撕裂。只有32位对齐的下半部分一直在变化,并且在原子上发生了变化,因为您的编译器保证uint64_t的至少4字节对齐,而x86保证4字节对齐的加载/存储是原子的。0
和-1ULL
是显而易见的选择。对于64位结构,我在this GCC C11 _Atomic bug的测试用例中使用了相同的内容。
对于您的情况,我会这样做。 read()
和write()
是POSIX系统调用名称,因此我选择了其他名称。
#include <cstdint>
volatile uint64_t sharedValue = 0; // initializer = one of the 2 values!
void writer() {
for (;;) {
sharedValue = 0;
sharedValue = -1ULL; // unrolling is vastly simpler than an if
}
}
void reader() {
for (;;) {
uint64_t val = sharedValue;
uint32_t low = val, high = val>>32;
if (low != high) {
std::cout << "Tearing! Value: " << std::hex << val << '\n';
}
}
}
movlpd
64位存储,但为
-1
使用两个单独的
= -1
32位存储。 (和读取器分别加载两个32位)。正如您所期望的那样,GCC在编写器中总共使用了四个
mov dword ptr [mem], imm32
存储。 (
Godbolt compiler explorer)
std::atomic<>
,您将只具有该花园品种的竞赛条件,而没有未定义的行为。
volatile
对象上的数据争用未定义行为(Undefined Behavior)。
数据争用UB是一个技术术语,比“种族条件” 具有更具体的含义。我更改了错误消息,以报告我们检查的一种症状。请注意,非
volatile
对象上的数据争用UB可能具有怪异的效果,例如托管加载或循环外存储,甚至发明了额外的读取操作,导致代码认为一次读取同时为真和为假。 (
https://lwn.net/Articles/793253/)
cout
刷新:一个来自
std::endl
,另一个来自
std::flush
。 cout默认为行缓冲,如果写入文件则为全缓冲,这很好。就DOS行尾而言,
'\n'
与
std::endl
一样可移植。文本与二进制流模式可以解决此问题。 endl仍然只是
\n
。
high = low = 0xff00ff00
这样的假阴性的合理方法。
So I guess that on x86 there is no need to use std::atomic for 32 bit data types?
volatile int
手动滚动原子不能给您原子RMW操作(没有内联asm或Windows
InterlockedIncrement
或GNU C内置
__atomic_fetch_add
内置的特殊功能),也不能给您任何排序保证。其他代码。 (发布/获取语义)
volatile
滚动您自己的原子,并且事实上已经得到许多主流编译器的支持(例如,Linux内核以及内联asm仍然可以这样做)。实际的编译器确实可以有效地定义
volatile
对象上的数据争用行为。但是,如果有一种便携式且可保证安全的方法,通常是个坏主意。只需将
std::atomic<T>
与
std::memory_order_relaxed
一起使用即可获得与ojit_code一样高效的asm(对于
volatile
可以使用的情况),但是可以保证ISO C++标准的安全性和正确性。
volatile
还允许您使用C++ 17
atomic<T>
或更旧的成员函数来询问实现是否可以廉价地实现原子类型。 (在实践中,C++ 11实现决定不让任何给定原子的实例但并非所有实例基于对齐方式或某些事物而被锁定;相反,它们只是给atom原子提供所需的alignas(如果存在)。因此,C++ 17实现了一个常量(按类型常量),而不是按对象成员函数检查锁自由的方法)。
std::atomic<T>::is_always_lock_free
还可以为比普通寄存器宽的类型提供便宜的无锁原子性。例如在ARM上,使用ARMv6的
std::atomic
/
strd
来存储/加载一对寄存器。
ldrd
进行原子的64位加载和存储来实现
std::atomic<uint64_t>
,而无需使用non-lock_free机制(锁表)。
实际上,GCC和clang9确实使用movq
加载/存储。不幸的是,clang8.0和更早版本使用
movq
。 MSVC以更低效的方式使用
atomic<uint64_t>
。在Godbolt链接中更改sharedVariable的定义以查看它。 (或者,如果您在循环中分别使用默认的seq_cst和
lock cmpxchg8b
存储库中的一个,则MSVC由于某种原因会为其中一个调用
lock cmpxchg8b
helper函数。但是,当两个存储库具有相同的顺序时,它将内联锁cmpxchg8b的循环比clang8更笨拙.0)请注意,这种效率低下的MSVC代码源是针对
memory_order_relaxed
不是原子的;在这种情况下,带有
?store@?$_Atomic_storage@_K$07@std@@QAEX_KW4memory_order@2@@Z
的
volatile
也可以很好地编译。
atomic<T>
获得宽原子的代码源。尽管GCC实际上确实将movq用于if()bool写函数(请参见前面的Godbolt编译器资源管理器链接),因为它看不到交替或其他东西。它还取决于您使用什么值。使用0和-1时,它将使用单独的32位存储,但是使用0和
mo_relaxed
时,您会得到movq来表示可用的模式。 (我用它来验证您仍然可以从读取端撕开,而不是手写一些asm。)我的简单展开版本编译为仅将简单的
volatile
存储与GCC一起使用。这是一个很好的例子,说明在这种详细程度下
0x0f0f0f0f0f0f0f0fULL
的真正编译方式为零。
mov dword [mem], imm32
也将保证原子对象的8字节对齐,即使普通volatile
可能只对齐了4字节。
atomic<uint64_t>
对象的数据争夺仍然是未定义的行为。 (使用信号处理程序的
uint64_t
竞赛除外。)
void reader() {
for (;;) {
uint64_t val = __atomic_load_n(&sharedValue, __ATOMIC_ACQUIRE);
uint32_t low = val, high = val>>32;
if (low != high) {
std::cout << "Tearing! Value: " << std::hex << val << '\n';
}
}
}
void writer() {
volatile int separator; // in a different cache line, has to commit separately
for (;;) {
sharedValue = 0;
_mm_mfence();
separator = 1234;
_mm_mfence();
sharedValue = -1ULL; // unrolling is vastly simpler than an if
_mm_mfence();
separator = 1234;
_mm_mfence();
}
}
volatile
具有更新的微代码,就像
volatile sig_atomic_t
一样,它会阻止乱序的exec并耗尽存储缓冲区。因此,以后的存储甚至不应该在后面的存储离开之前进入存储缓冲区。这实际上可能是一个问题,因为我们需要时间进行合并,而不仅仅是在存储区退休后尽快“毕业”就提交32位存储区)。
关于c++ - 我如何证明 volatile 分配不是原子的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60464359/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!