【问题标题】:Why does std::shared_ptr call my destructor twice?为什么 std::shared_ptr 两次调用我的析构函数?
【发布时间】:2020-12-15 22:54:14
【问题描述】:

在这个程序中,为什么第 14 行的析构函数会为同一个 mystruct_t 实例调用两次?

我假设这个程序中的所有指针操作都是线程安全的。我认为原子更新不适用于我的系统或编译器。 我在 MSVC 2017、MSVC 2019 和 clang 上试过这个

/* This crashes for me (line 19) */
#include <iostream>
#include <vector>
#include <thread>
#include <memory>
#include <chrono>
#include <assert.h>

struct mystruct_t {
    int32_t nInvocation = 0;
    ~mystruct_t();
    mystruct_t() = default;
};
mystruct_t::~mystruct_t() {
    nInvocation++;
    int nInvoke = nInvocation;
    if (nInvoke > 1) {
        /* destructor was invoked twice */
        assert(0);
    }
    /* sleep is not necessary for crash */
    //std::this_thread::sleep_for(std::chrono::microseconds(525));
}
std::shared_ptr<mystruct_t> globalPtr;
void thread1() {
    for (;;) {
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        std::shared_ptr<mystruct_t> ptrNewInstance = std::make_shared<mystruct_t>();
        globalPtr = ptrNewInstance;
    }
}
void thread2() {
    for (;;) {
        std::shared_ptr<mystruct_t> pointerCopy = globalPtr;
    }
}
int main()
{
    std::thread t1;
    t1 = std::thread([]() {
        thread1();
        });
    std::thread t2;
    t2 = std::thread([]() {
        thread2();
        });
    for (int i = 0;; ++i) {
        std::this_thread::sleep_for(std::chrono::microseconds(1000));
        std::shared_ptr<mystruct_t> pointerCopy = globalPtr;
        globalPtr = nullptr;
    }
    return 0;
}

【问题讨论】:

  • 您的代码有未定义的行为。您对globalPtr的修改和读取没有同步。
  • 你的同步/线程安全在哪里?
  • 这里为什么需要同步访问?
  • 因为标准是这么说的。
  • std::shared_ptr 的内部控制块是线程安全的。 std::shared_ptr 本身对于读写访问不是线程安全的。

标签: c++ multithreading thread-safety shared-ptr


【解决方案1】:

正如这里的几个用户已经提到的那样,您遇到了未定义的行为,因为您在全局(或外线程)更改您引用的对象,而线程本地,您尝试复制分配它。 sharedPtr 的一个缺点,特别是对于新手来说,是建议中相当隐蔽的危险,你在复制它们时总是线程安全的。由于您不使用引用,因此这可能会变得更加难以怀疑。始终尝试首先将 shared_ptr 视为常规类,并使用一个常见的(“微不足道的”)成员复制分配,其中在不受保护的线程环境中总是可能发生干扰。

如果您将来会遇到使用该代码或类似代码的类似情况,请尝试使用强大的频道广播/基于事件的方案,而不是本地放置的锁!通道(缓冲或基于单个数据)本身关心适当的数据生命周期,然后确保 sharedPtr 的基础数据“救援”。

【讨论】:

    猜你喜欢
    • 2013-01-12
    • 2021-11-06
    • 2011-02-07
    • 2015-07-26
    • 2017-05-24
    • 2017-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多