为什么 std::shared_ptr 两次调用我的析构函数?
Why does std::shared_ptr call my destructor twice?
在这个程序中,为什么第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;
}
正如这里的几个用户已经提到的,您 运行 陷入了未定义的行为,因为您在全局(或外线程)更改了您的引用对象,而在线程本地,您尝试复制分配它。 sharedPtr 的一个缺点,特别是对于新手来说,是建议中的隐患,您在复制它们时始终是线程安全的。由于您不使用参考资料,因此更难看出有疑问。始终尝试首先将 shared_ptr 视为常规 class,并使用常见的 ('trivial') 成员复制分配,其中在不受保护的线程环境中始终可能发生干扰。
如果您将来会遇到类似情况或类似代码,请尝试使用基于通道 broadcasting/event 的稳健方案,而不是本地放置的锁!通道(缓冲的或基于单一数据的)本身关心适当的数据生命周期,确保 sharedPtr 的基础数据 'rescue'.
在这个程序中,为什么第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;
}
正如这里的几个用户已经提到的,您 运行 陷入了未定义的行为,因为您在全局(或外线程)更改了您的引用对象,而在线程本地,您尝试复制分配它。 sharedPtr 的一个缺点,特别是对于新手来说,是建议中的隐患,您在复制它们时始终是线程安全的。由于您不使用参考资料,因此更难看出有疑问。始终尝试首先将 shared_ptr 视为常规 class,并使用常见的 ('trivial') 成员复制分配,其中在不受保护的线程环境中始终可能发生干扰。
如果您将来会遇到类似情况或类似代码,请尝试使用基于通道 broadcasting/event 的稳健方案,而不是本地放置的锁!通道(缓冲的或基于单一数据的)本身关心适当的数据生命周期,确保 sharedPtr 的基础数据 'rescue'.