C++ 线程竞争条件模拟
C++ threads race condition simulation
这是一个 C++ 程序,它使用 5
个不同的线程运行 10
次,每个线程递增 counter
的 value
,因此最终输出应该是 500
,这正是程序给出的输出。但我不明白为什么每次输出应该不同时它都会给出 500
因为增量操作不是 atomic
并且没有使用锁所以程序应该在每种情况下给出不同的输出。
编辑以增加竞争条件的可能性我增加了循环计数但仍然看不到任何不同的输出
#include <iostream>
#include <thread>
#include <vector>
struct Counter {
int value;
Counter() : value(0){}
void increment(){
value = value + 1000;
}
};
int main(){
int n = 50000;
while(n--){
Counter counter;
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i){
threads.push_back(std::thread([&counter](){
for(int i = 0; i < 1000; ++i){
counter.increment();
}
}));
}
for(auto& thread : threads){
thread.join();
}
std::cout << counter.value << std::endl;
}
return 0;
}
你真幸运:)
用 clang++ 编译我的输出并不总是 500:
500
425
470
500
500
500
500
500
432
440
仅仅因为您的代码具有竞争条件并不意味着它们会发生。这是关于他们的困难部分。很多时候只有当其他事情发生变化并且时间不同时它们才会发生。
这里有几个问题:增加到 100 可以非常快地完成。因此,在第二个线程开始之前,您的线程可能已经完成了一半。下一个线程等也是如此。所以你永远不知道你真的有 5 个并行。
您应该在每个线程的开头创建一个 barrier 以确保它们同时启动。
也可以尝试多于“100”且只有 5 个线程。但这完全取决于系统/负载/时序。等等
备注
将 g++ 与 -fsanitize=thread -static-libtsan 结合使用:
WARNING: ThreadSanitizer: data race (pid=13871)
Read of size 4 at 0x7ffd1037a9c0 by thread T2:
#0 Counter::increment() <null> (Test+0x000000509c02)
#1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
#2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
#3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
#4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
#5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)
Previous write of size 4 at 0x7ffd1037a9c0 by thread T1:
#0 Counter::increment() <null> (Test+0x000000509c17)
#1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
#2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
#3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
#4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
#5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)
显示竞争条件。 (此外,在我的系统上,输出显示的结果不同于 500)。
g++ 的选项在 g++ 的文档中进行了解释(例如:man g++)。另见:https://github.com/google/sanitizers/wiki#threadsanitizer.
to increase probability of race condition i increased the loop count
but still couldn't see any varying output
严格来说,您在此代码中存在数据竞争,这是未定义的行为,因此您无法可靠地重现它。
但您可以将 Counter
重写为某些 "equivalent" 代码,并在 increment
中人为延迟:
struct Counter {
int value;
Counter() : value(0){}
void increment(){
int val=value;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
++val;
value=val;
}
};
我用这个计数器得到了以下输出,它远小于 500
:
100
100
100
100
100
101
100
100
101
100
这是一个 C++ 程序,它使用 5
个不同的线程运行 10
次,每个线程递增 counter
的 value
,因此最终输出应该是 500
,这正是程序给出的输出。但我不明白为什么每次输出应该不同时它都会给出 500
因为增量操作不是 atomic
并且没有使用锁所以程序应该在每种情况下给出不同的输出。
编辑以增加竞争条件的可能性我增加了循环计数但仍然看不到任何不同的输出
#include <iostream>
#include <thread>
#include <vector>
struct Counter {
int value;
Counter() : value(0){}
void increment(){
value = value + 1000;
}
};
int main(){
int n = 50000;
while(n--){
Counter counter;
std::vector<std::thread> threads;
for(int i = 0; i < 5; ++i){
threads.push_back(std::thread([&counter](){
for(int i = 0; i < 1000; ++i){
counter.increment();
}
}));
}
for(auto& thread : threads){
thread.join();
}
std::cout << counter.value << std::endl;
}
return 0;
}
你真幸运:)
用 clang++ 编译我的输出并不总是 500:
500
425
470
500
500
500
500
500
432
440
仅仅因为您的代码具有竞争条件并不意味着它们会发生。这是关于他们的困难部分。很多时候只有当其他事情发生变化并且时间不同时它们才会发生。
这里有几个问题:增加到 100 可以非常快地完成。因此,在第二个线程开始之前,您的线程可能已经完成了一半。下一个线程等也是如此。所以你永远不知道你真的有 5 个并行。
您应该在每个线程的开头创建一个 barrier 以确保它们同时启动。
也可以尝试多于“100”且只有 5 个线程。但这完全取决于系统/负载/时序。等等
备注
将 g++ 与 -fsanitize=thread -static-libtsan 结合使用:
WARNING: ThreadSanitizer: data race (pid=13871)
Read of size 4 at 0x7ffd1037a9c0 by thread T2:
#0 Counter::increment() <null> (Test+0x000000509c02)
#1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
#2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
#3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
#4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
#5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)
Previous write of size 4 at 0x7ffd1037a9c0 by thread T1:
#0 Counter::increment() <null> (Test+0x000000509c17)
#1 main::{lambda()#1}::operator()() const <null> (Test+0x000000507ed1)
#2 _M_invoke<> /usr/include/c++/5/functional:1531 (Test+0x0000005097d7)
#3 operator() /usr/include/c++/5/functional:1520 (Test+0x0000005096b2)
#4 _M_run /usr/include/c++/5/thread:115 (Test+0x0000005095ea)
#5 <null> <null> (libstdc++.so.6+0x0000000b8c7f)
显示竞争条件。 (此外,在我的系统上,输出显示的结果不同于 500)。
g++ 的选项在 g++ 的文档中进行了解释(例如:man g++)。另见:https://github.com/google/sanitizers/wiki#threadsanitizer.
to increase probability of race condition i increased the loop count but still couldn't see any varying output
严格来说,您在此代码中存在数据竞争,这是未定义的行为,因此您无法可靠地重现它。
但您可以将 Counter
重写为某些 "equivalent" 代码,并在 increment
中人为延迟:
struct Counter {
int value;
Counter() : value(0){}
void increment(){
int val=value;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
++val;
value=val;
}
};
我用这个计数器得到了以下输出,它远小于 500
:
100
100
100
100
100
101
100
100
101
100