首页 > 解决方案 > 为什么 std::shared_ptr 两次调用我的析构函数?

问题描述

在这个程序中,为什么第 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;
}

标签: c++multithreadingthread-safetyshared-ptr

解决方案


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

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


推荐阅读