【发布时间】:2021-12-20 19:38:46
【问题描述】:
在这个代码示例中,我们将std::shared_ptr<int> 传递给一个线程,并期望它被弱化为std::weak_ptr<int>。但在 Visual Studio 2019 中,t 具有很强的参考意义,并且该程序的输出在调试和发布版本中都是“pw alive”。编译器g++ -lpthread在linux上也有同样的问题。
这个玩具示例的动机是更严重的情况,其中线程t 进行一些长时间的计算,然后通过共享资源发送结果t 作为参数接收。这些资源可能在t 的计算过程中被其他线程删除,在这种情况下t 变得无用并且可以提前终止。但是由于这个(疑似)错误,t 使资源保持活动状态并浪费内存和计算时间。
这真的是一个错误,还是我误解了 weak_ptr 和 shared_ptr 之间的关系?如果这是一个错误,您知道在哪里报告吗?对于 Visual Studio 和 g++,可能在不同的地方。
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
void TestWeakPtrAlive(std::weak_ptr<int> pw)
{
// wait for main thread to delete pw
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::shared_ptr<int> p = pw.lock();
std::cout << (p ? "pw alive" : "pw dead")
<< std::endl;
}
int main()
{
std::shared_ptr<int> p = std::make_shared<int>(18);
std::thread t(TestWeakPtrAlive, p);
p = nullptr;
t.join();
return 0;
}
【问题讨论】:
-
所以,你的问题是,“我尝试过的每个实现都做同样的事情,我认为这不是它应该做的,所以它们不都是错误的吗?”
-
按预期工作。线程通过值获取
p,存储它,然后线程函数开始获取基于存储的p值创建的weak_ptr。您有两个shared_ptr实例,一个在main函数中,第二个在线程中,所以weak_ptr必须处于活动状态(通过p = nullptr,只发布了一个,对象仍然活动,引用计数器为1)。 -
Re:“应该够长”——你不能通过猜测可能的情况来编写线程安全的代码。你的直觉几乎总是错误的。
p = nullptr;周围没有同步,所以程序的行为是未定义的。最糟糕的是,当你测试代码时它会“正常”工作,而当你为最重要的客户演示它时它会失败。 -
@V.Semeria -- 慢点。你并不像你想象的那么了解这件事。
std::thread构造函数只是复制它的参数;它不会通过查看线程函数来决定它期望的类型。在设置新线程的代码中,调用线程函数使用传递给线程构造函数的参数的副本。所以,是的,如果类型不可转换,那就是错误。不是因为参数不能传递给构造函数;他们可以而且他们是;但因为在构造函数内部无法转换对象。 -
一个很大的危险领域是通过引用来论证。在单个线程中,该参数的值在函数运行时不会更改,除非被调用的函数更改它。在线程函数中,构造函数返回后,创建线程可以修改该值,使该属性消失。复制该值可确保没有任何此类意外隐藏在表面之下。在线程之间共享一个对象需要做一些额外的事情,比如
std::ref(p),实际上是通过引用传递。
标签: c++ multithreading visual-c++ g++