C++ std::mutex使用示例
我编写了这段代码作为测试:C++ std::mutex使用示例,c++,multithreading,mutex,data-race,C++,Multithreading,Mutex,Data Race,我编写了这段代码作为测试: #include <iostream> #include <thread> #include <mutex> int counter = 0; auto inc(int a) { for (int k = 0; k < a; ++k) ++counter; } int main() { auto a = std::thread{ inc, 100000 }; auto
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
int main() {
auto a = std::thread{ inc, 100000 };
auto b = std::thread{ inc, 100000 };
a.join();
b.join();
std::cout << counter;
return 0;
}
事实就是这样。互斥解决方案给了我200000,这是正确的,因为一次只有一个威胁可以访问计数器。但是为什么非互斥解决方案仍然显示200000?数据竞争是未定义的行为,这意味着任何程序执行都是有效的,包括执行您想要的程序。在这种情况下,编译器可能正在将循环优化为
计数器+=a
,第一个线程在第二个线程开始之前完成,因此它们实际上不会发生冲突。这里的问题是,您的数据争用非常小。任何现代编译器,所以竞赛窗口都非常小——我甚至可以说,一旦启动第二个线程,第一个线程就已经完成了
这并没有减少未定义的行为,但解释了您看到的结果。您可能会降低编译器在循环方面的智能,例如通过使a
或k
或计数器
易失性
;然后您的数据竞争应该变得明显。竞争条件是未定义的行为
当涉及数据竞争时,您不能断言应该发生什么。你认为应该有一些明显的数据撕裂证据(即最终结果是178592或其他)的说法是错误的,因为没有理由期待任何这样的结果
您观察到的行为可能可以用编译器优化来解释
下面的代码
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
注意写入计数器的次数如何从O(a)
优化为O(1)
。这很重要。这意味着写入计数器
可能(也可能)在第二个线程初始化之前完成,这使得数据撕裂的观察在统计学上是不可能的
如果您想强制该代码按照预期的方式运行,请考虑将变量“代码>计数器< /COD>标记为<代码>易失性/代码>:
#include <iostream>
#include <thread>
#include <mutex>
volatile int counter = 0;
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
int main() {
auto a = std::thread{ inc, 100000 };
auto b = std::thread{ inc, 100000 };
a.join();
b.join();
std::cout << counter;
return 0;
}
#包括
#包括
#包括
易失性整数计数器=0;
汽车公司(INTA){
对于(int k=0;k如果行为未定义,它可以做任何事情,包括给出您期望的结果。它可能不在另一个平台上、另一个平台上或以后。可能增量运算符在您的平台上是原子的?对于此应用程序,您可以使用std::atomic counter;
。您的平台似乎有原子int
increment.std::atomic
可以移植利用它。@FrançoisAndrieux我使用VS2017和Intel i7700k。我的平台没有原子增量操作!我用Delphi编写了相同的代码,非互斥给我随机值如果你坚持猜测未定义的行为,你需要共享exa使用了ct编译器版本和编译标志。即使除了未定义的行为问题,即使在-O1
,gcc和clang都将您的函数实现为单个add
指令…因此您不太可能看到这些冲突…我甚至没有考虑过这种优化…谢谢!这就是我想要的答案当我看到C++正确地(非互斥的方式),而在另一种语言,Delphi中,由于未定义的行为(仍然是非互斥的方式),我能够重现随机输出数时,我的脑海中出现了麻烦。。也是的,使用volatile关键字,我再也得不到确切的数字了!即使没有优化。创建线程的成本很高。运行循环的成本很低。第一个线程可以在创建第二个线程之前完成所有迭代。@MartinYork因此,我在最后一段中建议需要注意的是,人们往往高估了线程创建的“成本”。这在很大程度上取决于OS+底层硬件,但即使我们假设增加一个变量是一个CPU周期(不太可能),通常一个线程可以在100000个CPU周期内完全创建。@Xirema:这个评论并不是针对你的(只是为了让其他人提供更多的上下文)。我知道你理解这个问题。我想说,创建线程最昂贵的部分是分配新的堆栈区域。内存管理是这里的瓶颈(即使你使用的是操作系统而不是语言内存处理程序),其余的看起来都相对简单。
auto inc(int a) {
counter += a;
}
#include <iostream>
#include <thread>
#include <mutex>
volatile int counter = 0;
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
int main() {
auto a = std::thread{ inc, 100000 };
auto b = std::thread{ inc, 100000 };
a.join();
b.join();
std::cout << counter;
return 0;
}