C++ 关于线程安全的困惑
我对并发的世界还不熟悉,但从我所读到的内容来看,我理解下面的程序在执行过程中没有定义。如果我理解正确,这不是线程安全的,因为我同时以非原子方式读取/写入共享的ptr和计数器变量C++ 关于线程安全的困惑,c++,c++11,thread-safety,C++,C++11,Thread Safety,我对并发的世界还不熟悉,但从我所读到的内容来看,我理解下面的程序在执行过程中没有定义。如果我理解正确,这不是线程安全的,因为我同时以非原子方式读取/写入共享的ptr和计数器变量 #include <string> #include <memory> #include <thread> #include <chrono> #include <iostream> struct Inner { Inner() {
#include <string>
#include <memory>
#include <thread>
#include <chrono>
#include <iostream>
struct Inner {
Inner() {
t_ = std::thread([this]() {
counter_ = 0;
running_ = true;
while (running_) {
counter_++;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
}
~Inner() {
running_ = false;
if (t_.joinable()) {
t_.join();
}
}
std::uint64_t counter_;
std::thread t_;
bool running_;
};
struct Middle {
Middle() {
data_.reset(new Inner);
t_ = std::thread([this]() {
running_ = true;
while (running_) {
data_.reset(new Inner());
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
});
}
~Middle() {
running_ = false;
if (t_.joinable()) {
t_.join();
}
}
std::uint64_t inner_data() {
return data_->counter_;
}
std::shared_ptr<Inner> data_;
std::thread t_;
bool running_;
};
struct Outer {
std::uint64_t data() {
return middle_.inner_data();
}
Middle middle_;
};
int main() {
Outer o;
while (true) {
std::cout << "Data: " << o.data() << std::endl;
}
return 0;
}
#包括
#包括
#包括
#包括
#包括
结构内部{
内(){
t=std::thread([this](){
计数器=0;
运行=真;
当(运行时){
计数器++;
std::this_线程::sleep_for(std::chrono::毫秒(10));
}
});
}
~Inner(){
运行=错误;
if(t_u2;.joinable()){
t_.join();
}
}
std::uint64\u t计数器;
标准:螺纹t;
布尔跑步;
};
结构中间{
中(){
数据重置(新内部);
t=std::thread([this](){
运行=真;
当(运行时){
数据重置(新的内部());
std::this_线程::sleep_for(std::chrono::毫秒(1000));
}
});
}
~Middle(){
运行=错误;
if(t_u2;.joinable()){
t_.join();
}
}
std::uint64\u t内部数据(){
返回数据->计数器;
}
std::共享的ptr数据;
标准:螺纹t;
布尔跑步;
};
结构外部{
std::uint64_t数据(){
返回中间\内部\数据();
}
中段;
};
int main(){
外o;
while(true){
线程安全是线程之间的操作,一般来说不是绝对的
当另一个线程写入变量时,如果另一个线程的写入与您的读取或写入不同步,则无法读取或写入变量。这样做是未定义的行为
未定义可能意味着任何事情。程序崩溃。程序读取不可能的值。程序格式化硬盘驱动器。程序通过电子邮件将浏览器历史记录发送给所有联系人
非同步整数访问的一种常见情况是,编译器将一个值的多个读取优化为一个值,而不重新加载该值,因为它可以证明没有定义的方式可以修改该值。或者,CPU内存缓存也会执行相同的操作,因为您没有同步
对于指针,可能会出现类似或更糟糕的问题,包括跟随悬空指针、损坏内存、崩溃等
,以及
中间::内部\u数据中的数据访问->计数器安全吗
不,这是一种竞争条件。根据标准,任何时候允许从多个线程对同一变量进行非同步访问,并且至少有一个线程可能会修改该变量,这都是未定义的行为
作为一个实际问题,以下是一些您可能会看到的不想要的行为:
读取计数器值的线程读取计数器的“旧”值(很少或从不更新),这是因为不同的处理器内核独立地缓存变量(使用atomic_t可以避免此问题,因为这样编译器就会知道您打算以非同步方式访问此变量,并且它会知道采取预防措施来防止此问题)
当线程A被线程B踢出CPU时,线程A可能读取数据
共享\u指针指向的地址,并且即将取消对地址的引用,并从它指向的内部
结构中读取。线程B执行,在线程B执行期间,旧的内部结构被删除,而data_u
shared_指针设置为指向新的内部结构。然后线程a再次回到CPU上,但由于线程a在内存中已经有旧的指针值,因此它取消引用旧的值而不是新的值,并最终从释放/无效的内存中读取。同样,这是未定义的行为,因此原则上任何事情都可能发生;在实践中,您可能会看到没有明显的错误行为,或者偶尔出现错误/垃圾值,或者可能出现崩溃,这取决于
如果线程A有一个成员共享\u ptr sp并决定更新它
线程B执行共享时,sp=A::sp将复制
销毁是线程安全的吗?还是因为
对象正在被销毁
如果您只重定共享对象本身的目标(即将它们更改为指向不同的对象),而不修改它们指向的T对象,那么这应该是线程安全的。但是如果您正在修改T对象本身的状态(即示例中的内部对象)这不是线程安全的,因为一个线程可以读取对象,而另一个线程正在写入对象(删除对象可以被视为写入对象的特例,因为它肯定会更改对象的状态)
在什么情况下(我能用工具检查一下吗?)
未定义可能意味着std::终止
当您遇到未定义的行为时,它在很大程度上取决于您的程序、编译器、操作系统和硬件体系结构的细节。原则上,未定义的行为意味着任何事情(包括按您的预期运行的程序!)可能发生,但你不能依赖任何特定的行为——这就是为什么未定义的行为如此邪恶
特别是,具有竞争条件的多线程程序通常会在数小时/数天/数周内正常运行,然后某一天时间恰好合适,程序崩溃或计算出错误的结果。因此,竞争条件很难重现
至于何时可以调用terminate(),如果故障导致运行时环境检测到的错误状态(即,它破坏运行时环境对其进行完整性检查的数据结构,例如,在某些实现中),则将调用terminate()