C ++使用指向std :: shared_ptr的Raw指针

时间:2020-04-02 17:34:47

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

因此,我试图通过我正在使用的消息传递机制在线程之间传递共享指针。由于序列化/反序列化的工作方式,我无法直接将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管理的映射中(我试图忽略了最初认为不重要的细节)。但是,我没有意识到的是,我从上述地图检索的方式是不正确的。我将映射的内容复制到temp shared_ptr中,然后将temp shared_ptr的地址发送到thread2。因此,我无意间发送了堆栈上变量的地址。愚蠢的错误,但是我认为此主题中的评论还是很有启发性/帮助的。以下内容似乎可以解决我的问题。

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

2 个答案:

答案 0 :(得分:4)

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

为了转让shared_ptr实例的所有权,我建议创建一个堆分配/动态shared_ptr进行转让。可以使用new或(甚至更好)make_unique来完成。使用unique_ptr(即:unique_ptr<shared_ptr<ObjectClass>>),您需要先使用“释放”方法,然后再将指针穿过消息中的线程屏障。

线程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<shared_ptr<T>>中来进一步缩短此时间,但是我个人更喜欢这种方法,因为它具有清晰的“捕获”和“释放飞行中指针的语义。

答案 1 :(得分:2)

您的代码将一个临时共享指针的地址从一个线程发送到另一个线程。您将public class MyTaskHandler : DailyTaskHandler { private readonly IMyService _myService; public MyTaskHandler(IMyService myService, IDailyTasksScheduler dailyTasksScheduler) : base(dailyTasksScheduler) { _myService = myService; } public override int Hour => base.Hour; // you can override default hour public override void Process() => _myService.DoStuff(); } 创建为堆栈上的对象,并将public abstract class DailyTaskHandler : IDailyTaskHandler, IScheduledTaskHandler { private readonly IDailyTasksScheduler _dailyTasksScheduler; protected DailyTaskHandler(IDailyTasksScheduler dailyTasksScheduler) { _dailyTasksScheduler = dailyTasksScheduler; Logger = NullLogger.Instance; TaskType = GetType().FullName; } public ILogger Logger { get; set; } public virtual int Hour { get; } = 1; // default scheduled hour of the day public string TaskType { get; } public void Process(ScheduledTaskContext context) { if (context.Task.TaskType == TaskType) { Logger.Information($"Process task: {TaskType}"); try { Process(); } catch (Exception e) { Logger.Error(e, e.Message); } finally { _dailyTasksScheduler.Schedule(this); } } } public abstract void Process(); } public class DailyTasksStarter : IOrchardShellEvents { private readonly IEnumerable<IDailyTaskHandler> _dailyTaskHandlers; private readonly IDailyTasksScheduler _dailyTasksScheduler; public DailyTasksStarter( IEnumerable<IDailyTaskHandler> dailyTaskHandlers, IDailyTasksScheduler dailyTasksScheduler) { _dailyTaskHandlers = dailyTaskHandlers; _dailyTasksScheduler = dailyTasksScheduler; } public void Activated() => _dailyTasksScheduler.Schedule(_dailyTaskHandlers); public void Terminating() { } } public class DailyTasksScheduler : IDailyTasksScheduler { private readonly IScheduledTaskManager _scheduledTaskManager; public DailyTasksScheduler(IScheduledTaskManager scheduledTaskManager) { _scheduledTaskManager = scheduledTaskManager; } public void Schedule(IDailyTaskHandler dailyTaskHandler) => Schedule(new IDailyTaskHandler[] { dailyTaskHandler }); public void Schedule(IEnumerable<IDailyTaskHandler> dailyTaskHandlers) { DateTime nextDay = DateTime.UtcNow.AddDays(1); foreach (var dailyTaskHandler in dailyTaskHandlers) { DateTime nextTaskDate = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, dailyTaskHandler.Hour, 0, 0, DateTimeKind.Utc); if (nextTaskDate > DateTime.UtcNow && _scheduledTaskManager.GetTasks(dailyTaskHandler.TaskType)?.Any() != true) { _scheduledTaskManager.CreateTask(dailyTaskHandler.TaskType, nextTaskDate, null); } } } } public interface IDailyTaskHandler : IDependency { int Hour { get; } string TaskType { get; } } public interface IDailyTasksScheduler : IDependency { void Schedule(IDailyTaskHandler dailyTaskHandler); void Schedule(IEnumerable<IDailyTaskHandler> dailyTaskHandlers); } 的地址(而不是对象的地址!)传递给线程。

这不是您想要的。您有两个明智的选择:

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

  2. 使用IOrchardShellEvents为对象动态分配一个额外的共享指针,并将该共享指针传递给线程。完成对象后,使线程objPtr成为共享指针。这可能是确保对象寿命足够的最简单的修复方法。

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

相关问题