将shared_ptr传递给线程

Passing shared_ptr to thread

本文关键字:线程 ptr shared      更新时间:2023-10-16

我有以下代码:

shared_ptr<A> a;
B b(a);
a.reset(new A());

而在 B 类中,我创建了一个新线程并等待 a "准备就绪":

B(shared_ptr<A> a) {
    _a = a;
    //create thred here
}
//...
//in thread:
while (_this->_a == NULL) {}

问题是,即使在 a.reset(new A(((; 行被执行之后,在 B 中启动的线程仍然认为 _this->_a 是空的,永远不会离开 while 循环!

我尝试使用 a = make_shared(new A(((;

a = shared_ptr(new A(((; - 效果仍然相同。我也试图传递一个作为常量引用 - 同样的事情。

最有趣的是,当我使用纯指针并将对该指针的引用传递给类 B 时,一切正常:

A* a;
B b(a);
a = new A();
//...
B(A*& a) {
    _a = a;
    //create thred here
}
//in thread:
while (_this->_a == NULL) {}
//In this case the loop is finished

我在这里错过了什么?

更新(01.05.2014(:我用这样的代码解决了这个问题:

typedef shared_ptr<ClassA> ClassAPtr;
typedef shared_ptr<ClassB> ClassBPtr;
typedef shared_ptr<ClassC> ClassCPtr;
//...
ClassAPtr a(new ClassA());
ClassBPtr b( new ClassB() );
b->attachA(a);
a->attachB(b);
ClassCPtr c( new ClassC() );
c->attachB(b);
c->attachA(a);
c->run();
b->run();
a->run();

在每个类中,我都有原子变量_running:

atomic<bool> _running;

此外,我在每个线程循环中都有以下同步代码:

while (
        _this->_a == NULL || !_this->_a->isRunning()
        ||
        _this->_b == NULL || !_this->_b->isRunning()
        )
        std::this_thread::sleep_for(std::chrono::milliseconds(500));

您正在将shared_ptr<A>的副本传递给B的实例。当您通过调用reset将新对象分配给a时,存储在b中的副本不知道这已经发生了。这就是为什么它永远不会看到更新的原因。

调用 shared_ptr::reset 会使该 shared_ptr 实例放弃托管对象的所有权,这意味着如果该实例恰好是唯一所有者,则递减use_count并销毁对象。如果它不是唯一的所有者,则管理该对象的其他shared_ptr将负责管理其生存期。无论哪种情况,您调用reset的实例现在可以自由地获得您可能已作为参数传递的另一个对象的所有权。在您的情况下,这一切都更简单一些,因为最初没有管理对象,但相同的逻辑适用。存储在 b 中的副本在调用 reset 后不会链接到a

假设你的类A实现了两个成员函数,activate()is_active() ,具有明显的功能。还假设构造 A 的实例会使其处于停用状态,直到您调用 activate() 。然后你可以按如下方式解决这个问题:

auto a = make_shared<A>();
B b(a);
a->activate();
// within the thread
while (!_this->_a->is_active()) {}

即便如此,您仍需要使用一些同步原语来防止数据竞争。例如,如果activate()设置了一个布尔数据成员,则该成员的类型应该是std::atomic<bool>std::atomic_flag,而不是普通bool。这同样适用于可能从不同线程读取和写入的任何其他数据成员。

std::shared_ptr不是线程安全的。您需要使用不同的同步机制。 std::future 是一个可用于等待另一个线程的对象。您可以使用 std::packaged_task 创建一个。

可能发生的情况是,本地副本由共享指针的内部组成。此缓存副本不知道主内存中的更新。指针已更新,但这取决于哪个编译器、哪些优化处于活动状态以及 CPU 体系结构。