传递指向shared_ptr线程安全的指针

Passing pointer to shared_ptr thread safety

本文关键字:线程 安全 指针 ptr shared      更新时间:2023-10-16
// Example program
#include <iostream>
#include <string>
#include <memory>
#include <atomic>
struct Smth {};
struct B { //Used in thread 2
B(std::shared_ptr<Smth>* smth) : smth_(smth) {};
~B();
void DoSomething() {
std::shared_ptr<Smth> newSmth = std::make_shared<Smth>();
smth_->swap(newSmth);
}
std::shared_ptr<Smth>* smth_;
};
struct A { //Used in thread 1&3
A() : smth_(std::make_shared<Smth>()), b_(&smth_) {};
~A();
std::shared_ptr<Smth> smth_;
B b_;
};

那么,将指针传递给shared_ptr以便我可以在单独的线程中交换shared_ptr的内容是否是一种好的做法? 第二个问题是我应该使用 std::atomic_store 来替换 shared_ptr::swap,如果是这样,我应该怎么做?

我的想法是令人难以置信的,你最终会得到这个设计选择......我希望它在你的实际项目中是有意义的。

您应该将指针(或最好是引用)传递给shared_ptr的唯一原因是重新放置它(就像您正在做的那样),并且您不需要同步交换本身(shared_ptr已经同步)。

但是,交换内容将导致与当时使用该对象(读取或写入)的其他线程发生同步问题。因此,您需要应用与两个线程都在修改对象时相同的同步技术。

在我看来,这都不像是解决您实际问题的可靠解决方案。也许考虑将新std::shared_ptr传递给另一个线程,然后发出信号,以便它知道拾取新的共享指针(通过复制而不是指针)。

与(一些)传统观点相反,shared_ptr对象本身不是线程安全的。也就是说,您不能在不同线程上同时操作1个单个shared_ptr对象,就像您不能同时操作具有"默认"线程安全性的std::vectorstd::string或任何其他标准库对象一样。

出现混淆是因为典型的shared_ptr确实使用原子操作来操作其引用计数 - 但这只是为了支持不同shared_ptr对象指向同一基础对象(因此共享引用计数)的情况。

这种隐藏共享与过去发生在std::string的 COW 实现中的事情相同:您永远无法在不锁定的情况下安全地操作不同线程上的相同std::string对象,但 COW 行为意味着如果没有原子操作,您甚至无法操作两个不同的std::string对象,它们恰好在不同线程上共享相同的底层缓冲区, 违反std::string对象的值语义和标准库线程安全保证。因此,在实践中,您有原子操作来操作 COW 缓冲区引用计数,但其他操作像往常一样以非线程安全的方式编写。

所以底线是,共享这样的shared_ptr可能不仅是一个糟糕的设计:它是明确不允许的,因为它不是线程安全的。


1此处并发操作意味着访问多个线程上的shared_ptr对象,其中至少有一些访问是非const方法。标准库提供了仅包含const方法访问的并发访问是安全的(即,从线程角度来看,const充当操作是"只读"的标志)。