C++ 关于线程安全的困惑

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() {

我对并发的世界还不熟悉,但从我所读到的内容来看,我理解下面的程序在执行过程中没有定义。如果我理解正确,这不是线程安全的,因为我同时以非原子方式读取/写入共享的ptr和计数器变量

#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()