因此,我试图通过我正在使用的消息传递机制在线程之间传递共享指针。由于序列化/反序列化的工作方式,我无法直接将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);
答案 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);
}
的地址(而不是对象的地址!)传递给线程。
这不是您想要的。您有两个明智的选择:
将对象的地址从一个线程发送到另一个线程,并确保始终存在至少一个指向该对象的共享指针(就像您声明的代码一样)。这是一个可怕的修复,因为它不能确保对象的生存期足够。
使用IOrchardShellEvents
为对象动态分配一个额外的共享指针,并将该共享指针传递给线程。完成对象后,使线程objPtr
成为共享指针。这可能是确保对象寿命足够的最简单的修复方法。
将对象传递给线程的一般模式是创建一个类或结构,该类或结构携带要传递给线程的所有信息。在创建线程之前,请使用objPtr
创建该结构的新实例并根据需要填写它。然后将该结构的地址传递给线程。用结构完成线程后,使线程使用new
释放结构。您可以使用它来将对象或数据的任何集合传递给线程。在这种情况下,您想将实际的delete
传递给线程-而不是一个地址。