【问题标题】:C++ Using Raw pointer to std::shared_ptrC++ 使用指向 std::shared_ptr 的原始指针
【发布时间】:2020-04-02 17:34:47
【问题描述】:

所以我试图通过我正在使用的消息传递机制在线程之间传递一个共享指针。由于序列化/反序列化的工作方式,我无法将 shared_ptr 直接嵌入到我发送的消息中。所以我实际上需要发送一个 shared_ptr 的原始指针。见下文:

线程 1:

auto objPtr = std::make_shared<ObjectClass>();
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);

线程 2:

std::shared_ptr<ObjectClass> objPtrT2 = *(reinterpret_cast<std::shared_ptr<ObjectClass>*>(serializedPtr));

这有时会在线程 2 中在增加共享指针的引用计数时崩溃。我只能假设这里有一些竞争条件,但无法找出解决方案。请注意,它并不总是崩溃,反序列化似乎总是成功的。

我是否需要同步对此 shared_ptr 的访问(shared_ptr 本身,而不是 share_ptr 指向的内容)?我担心我传输此 shared_ptr 的方式会破坏引用计数的管理方式。

出于其他与性能相关的原因,我仍在争论在此处使用 shared_ptr 是否合适,但为了我自己的利益,我想知道我做错了什么。

谢谢

编辑: 请注意,线程 1 和线程 2 位于同一进程/主机中。我将 shared_ptr 放置到由 thread1 管理的地图中(我试图省略我最初认为不重要的细节)。然而,我没有意识到的是,我从所述地图中检索的方式是不正确的。我将地图的内容复制到临时 shared_ptr 中,然后将临时 shared_ptr 的地址发送到 thread2。所以我无意中发送了堆栈上变量的地址。愚蠢的错误,但我认为这个帖子中的评论仍然很有启发性/有帮助。以下似乎解决了我的问题。

auto& objPtr = m_objMap[index];
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);

【问题讨论】:

  • 为什么需要序列化?线程共享内存;为什么不直接传递共享指针?
  • 是的,我同意。我在这里省略了一些细节。 objPtr 存储在线程 1 中维护的映射中。线程 2 将 shared_ptr 存储/维护在不同的数据结构中。所以对象不应该被破坏(尽管我将通过调试器进行验证)。
  • @AndrewP 请注意,您传递的是指向shared_ptr&lt;T&gt; 而不是T 的指针,因此实际的shared_ptr 需要仍然存在。确保您的容器没有使其迭代器失效并且没有被移动。
  • 是的,如果shared_ptr在两个或多个线程之间共享后可以被任何线程修改,您确实需要同步访问。
  • 两个线程是否在同一个进程中?

标签: c++ multithreading thread-safety shared-ptr race-condition


【解决方案1】:

shared_ptr 在您复制它时会自动增加和减少其内部存储的引用计数(使用赋值 operator=),而且 - 重要的是 - 当 shared_ptr 被销毁时(通过超出范围)。您传输指向共享指针的指针的方法在上面的代码中存在根本缺陷,因为您传输的是临时共享指针的地址,而不是所有权或寿命时间>。 shared_ptr - 仍然由线程 A 拥有 - 可能会超出范围并在线程 B 可以使用它之前被销毁。

为了转让shared_ptr 实例的所有权,我建议创建一个堆分配/动态shared_ptr 进行转让。这可以使用new 或(甚至更好)make_unique 来完成。使用 unique_ptr(即:unique_ptr&lt;shared_ptr&lt;ObjectClass&gt;&gt;),在将指针传递到消息中的线程屏障之前,您将使用“释放”方法。

线程 A:

auto sharedPtr = std::make_shared<ObjectClass>();

// This line creates a heap-allocated copy of a 
// shared_ptr (incrementing reference count)
// And places ownership inside a unique_ptr
auto sharedPtrDynamicCopy = std::make_unique<decltype(sharedPtr)>(sharedPtr);

// This 'releases' ownership of the heap-allocated shared_ptr,
// returning a raw pointer; it is now a potential
// memory leak!!!  It must be 'captured' in Thread B.
auto rawPtrToPass = sharedPtrDynamicCopy.release();

线程 B:

// Here, we immediately 'capture' the raw pointer back
// inside a unique_ptr, closing the loop on the potential
// memory leak
auto sharedPtrDynamicCopy = unique_ptr<shared_ptr<ObjectClass>>(rawPtrFromThreadA);

// Now we can make a copy of the shared_ptr, if we like.
// This sharedCopy will live on, even after recvdPtr goes
// out of scope.
auto sharedCopy = *sharedPtrDynamicCopy;

您或许可以通过简单地“新建”原始shared_ptr 而不是在unique_ptr&lt;shared_ptr&lt;T&gt;&gt; 中捕获它来进一步缩短此时间,但我个人更喜欢这种方法,因为它具有明确的“捕获”和“释放”语义飞行中的指针。

【讨论】:

  • 假设thread1和thread2来自同一个进程(OP的“通过TCP/IP在计算机之间传递消息”的要求没有意义......),为什么thread2不能从sharedPtr创建一个shared_ptr直接在thread1中创建?
【解决方案2】:

您的代码将临时共享指针的地址从一个线程发送到另一个线程。您将objPtr 创建为堆栈上的对象,并将objPtr 的地址(不是对象的地址!)传递给线程。

这不是你想做的。你有两个明智的选择:

  1. object 的地址从一个线程发送到另一个线程,并确保始终存在至少一个指向该对象的共享指针(正如您声称您的代码已经存在的那样)。这是一个可怕的修复,因为它不能确保对象的生命周期是足够的。

  2. 使用new 动态分配指向对象的附加共享指针,并将指向该共享指针的指针传递给线程。使用对象完成后,让线程delete 共享指针。这可能是确保对象的生命周期足够的最简单的修复方法。

将对象传递给线程的一般模式是创建一个类或结构来承载您想要传递给线程的所有信息。在创建线程之前,使用new 创建结构的新实例并根据需要进行填充。然后将该结构的地址传递给线程。当线程完成结构后,让线程使用delete 释放结构。您可以使用它来将任何对象或数据集合传递给线程。在这种情况下,您希望将实际的shared_ptr 传递给线程——而不是一个地址。

【讨论】:

  • 在unique_ptr 和shared_ptr 存在的世界中,我们尽量避免使用new 和delete。更喜欢带有“释放”的 unique_ptr,因为这传达了捕获和传输语义。
  • @BTownTKD 创建一个unique_ptr 只是为了立即调用release 似乎毫无意义。处理 unique_ptrshared_ptr 对我来说似乎有点恶心。
  • 我猜,如果表达性代码毫无意义......?有关如何创建 unique_ptr 的信息,请参阅我的答案。
  • 我看不出与new 完全相同的两行代码如何比new 更具表现力。
  • 另外:您提供的第一个选项只是糟糕的建议。您不能传递对象的地址并使其由两个单独的 shared_ptrs 拥有。这肯定会导致双重删除。
猜你喜欢
  • 2012-09-07
  • 2021-11-15
  • 2021-11-05
  • 1970-01-01
  • 2013-08-18
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多