std::weak_ptr<T>::lock 线程安全吗?
Is std::weak_ptr<T>::lock thread-safe?
下面是一些示例代码,展示了我的用例。我有一个 PIMPL,可以在其中共享实现(它只是一堆昂贵的数据),但可以在不再需要时销毁实现。 class HasImpl
的实例使用指向 Impl
的共享指针,并且 class 定义包含一个 static weak_ptr
到 Impl
作为"pointer dispenser" 到 HasImpl
的新实例,给它们一个 Impl
的句柄(如果已经存在的话)。
该示例有两种调用 weak_ptr::lock
的方法——一种假设下面问题 1-3 的答案都是 "yes",另一种则不然。我更喜欢 weak_ptr::lock
是线程安全的唯一原因是可能有多个线程试图获取指向 Impl
的指针的副本,并且如果 lock
是线程安全的, 大多数执行线程不必传递静态变量定义(线程必须检查它是否已经初始化)并且不必竞争获取互斥量。
/* In HasImpl.h */
class HasImpl {
public:
HasImpl();
private:
class Impl;
static std::weak_ptr<Impl> sharedImplDispenser;
std::shared_ptr<Impl> myPtrToSharedImpl;
}
/* In HasImpl.cpp */
class HasImpl::Impl {
public:
Impl(); //constructor that takes a lot of time to run
//Lots of stuff, expensively produced, accessable to HasImpl through a shared_ptr to Impl
}
/* hypothetical constructor if weak_ptr::lock is thread-safe */
HasImpl::HasImpl() : myPtrToSharedImpl{sharedImplDispenser.lock()}
{
if (!myPtrToSharedImpl) {
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImplDispenser.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
}
/* hypothetical constructor if weak_ptr::lock is not thread-safe */
HasImpl::HasImpl()
{
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImpl.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
- 假设
std::weak_ptr
不为空并且在遥远过去的某个时候被分配了一个指针,如果一个线程调用 weak_ptr::lock
而另一个线程可能正在调用 [=19],控制块是否正常=]?
- 调用
weak_ptr::lock
而另一个线程可能正在将 ptr 分配给空的 weak_ptr 足够安全吗?也就是说,值是 return nullptr 还是新指针?我不关心 nullptr 是否是虚假的(也就是说,分配已经发生但其他线程还不知道)。我只是不想破坏控制块或从调用中获取无效指针值。
- 在销毁对象的最后一个 shared_ptr 时调用
weak_ptr::lock
是否线程安全?
- 如果 1 到 3 有问题,C++20 中的
std::atomic<std::weak_ptr<T>>
会解决这个问题吗?
标准明确表示 weak_ptr::lock
是 "executed atomically"。这样就回答了 1 和 3。
对于 #2,如果您询问分配给 same weak_ptr
,那么这是一场数据竞赛。更改共享状态 use_count
的操作不会引发数据竞争,但复制或操纵 weak_ptr
本身所做的不仅仅是戳 use_count
.
但是,如果您谈论的是锁定一个 weak_ptr
而同时取消另一个 weak_ptr
,这两个都在谈论相同的共享状态,那很好。两者仅通过共享状态的计数进行交互,据说没有问题。
是的,atomic<weak_ptr<T>>
允许您从多个线程操作同一个对象。
下面是一些示例代码,展示了我的用例。我有一个 PIMPL,可以在其中共享实现(它只是一堆昂贵的数据),但可以在不再需要时销毁实现。 class HasImpl
的实例使用指向 Impl
的共享指针,并且 class 定义包含一个 static weak_ptr
到 Impl
作为"pointer dispenser" 到 HasImpl
的新实例,给它们一个 Impl
的句柄(如果已经存在的话)。
该示例有两种调用 weak_ptr::lock
的方法——一种假设下面问题 1-3 的答案都是 "yes",另一种则不然。我更喜欢 weak_ptr::lock
是线程安全的唯一原因是可能有多个线程试图获取指向 Impl
的指针的副本,并且如果 lock
是线程安全的, 大多数执行线程不必传递静态变量定义(线程必须检查它是否已经初始化)并且不必竞争获取互斥量。
/* In HasImpl.h */
class HasImpl {
public:
HasImpl();
private:
class Impl;
static std::weak_ptr<Impl> sharedImplDispenser;
std::shared_ptr<Impl> myPtrToSharedImpl;
}
/* In HasImpl.cpp */
class HasImpl::Impl {
public:
Impl(); //constructor that takes a lot of time to run
//Lots of stuff, expensively produced, accessable to HasImpl through a shared_ptr to Impl
}
/* hypothetical constructor if weak_ptr::lock is thread-safe */
HasImpl::HasImpl() : myPtrToSharedImpl{sharedImplDispenser.lock()}
{
if (!myPtrToSharedImpl) {
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImplDispenser.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
}
/* hypothetical constructor if weak_ptr::lock is not thread-safe */
HasImpl::HasImpl()
{
static std::mutex mtx;
std::lockguard<std::mutex> lck(mtx);
myPtrToSharedImpl = sharedImpl.lock();
if (!myPtrToSharedImpl) {
const auto new_impl{std::make_shared<Impl()};
sharedImplDispenser = new_impl; // the only place in the program where a value is assigned to sharedImplDispenser
myPtrToSharedImpl = new_impl;
}
}
- 假设
std::weak_ptr
不为空并且在遥远过去的某个时候被分配了一个指针,如果一个线程调用weak_ptr::lock
而另一个线程可能正在调用 [=19],控制块是否正常=]? - 调用
weak_ptr::lock
而另一个线程可能正在将 ptr 分配给空的 weak_ptr 足够安全吗?也就是说,值是 return nullptr 还是新指针?我不关心 nullptr 是否是虚假的(也就是说,分配已经发生但其他线程还不知道)。我只是不想破坏控制块或从调用中获取无效指针值。 - 在销毁对象的最后一个 shared_ptr 时调用
weak_ptr::lock
是否线程安全? - 如果 1 到 3 有问题,C++20 中的
std::atomic<std::weak_ptr<T>>
会解决这个问题吗?
标准明确表示 weak_ptr::lock
是 "executed atomically"。这样就回答了 1 和 3。
对于 #2,如果您询问分配给 same weak_ptr
,那么这是一场数据竞赛。更改共享状态 use_count
的操作不会引发数据竞争,但复制或操纵 weak_ptr
本身所做的不仅仅是戳 use_count
.
但是,如果您谈论的是锁定一个 weak_ptr
而同时取消另一个 weak_ptr
,这两个都在谈论相同的共享状态,那很好。两者仅通过共享状态的计数进行交互,据说没有问题。
是的,atomic<weak_ptr<T>>
允许您从多个线程操作同一个对象。