在作为多线程 C++ 代码一部分的对象的构造函数中执行深度复制的最佳方法是什么?
3 回答
取决于您的数据结构。
我猜您面临的问题(尽管您没有这么说)是潜在的锁定反转。如果您正在深度复制,那么您可能会为需要复制的各种对象使用一个或多个锁。
如果您可以定义一个 DAG(即偏序),其中节点是系统中的每个锁,并且您可能想要使用的每个锁组合都由边连接,那么您可以确保永远不会使用锁不同线程中的不同顺序。因此,特别是您不会获得锁定反转。一个典型的规则是最后使用“最通用”的锁,因为这样可以最大限度地减少争用。
但是,如果您要深度复制一大堆“WidgetBox”中的一个,每个“WidgetBox”都包含基本上无法区分的“Widget”,并且框的内容之间可能存在重叠,那么您自然会遇到定义锁定顺序的问题。此外,您必须先锁定 WidgetBox(即使它是“最通用的”对象),因为没有该锁定,您无法确定还需要锁定什么。如果小部件具有可比性,您可能可以按顺序锁定每个小部件,复制并释放所有内容。讨厌。
另一种方法是定义在所有 Widget 和 WidgetBox 之间共享的单个锁。这可能会引起很多争用,在这种情况下,乐观锁定可能会有所改善,前提是副本不会同时发生太多修改。
另一种选择可能是放宽您对副本所做的保证 - 而不是要求从深层结构的可识别状态制作完整副本,您可以首先锁定 WidgetBox,浅复制它(使用引用计数或其他方式 - 锁定在引用计数上通常是“最终内部锁”,因此没有反转风险),释放 WidgetBox 锁,然后依次复制每个 Widget。如果 Widget 具有内部结构,请使用相同的方法来复制它们。结果可能包含一个 Widget,直到它从另一个线程中的 WidgetBox 中删除后才实现,或者其他类似的不协调,所以如果这是不可接受的,那么你不能使用这种方法。但是,如果您在每个线程中一次只锁定一个对象,那么您将无法获得锁定反转。
最后一个可能的“核”选项是使所有内容不可变,并始终在修改时复制。如果什么都不能修改,那么您就不需要任何锁(尽管在线程之间传递引用时仍然需要内存屏障)。
如果这些都不起作用,那么除非我忘记了某些东西,否则您将失去我的经验。我推测数据库实现是很多与锁相关的聪明才智的地方,这将是寻求想法的领域。
叉子()
我只是在开玩笑。但这太有趣了,不能放弃:)
我认为 onebyone 已经涵盖了您的大部分选择。除了,也许,退后一步,看看你是否能找到一种方法来避免一开始就做深拷贝......
我的第一个冲动(我不是专家):
锁定代码用于写入该对象的对象。当您进行深度复制时,锁定对象,执行您的深度复制,然后解锁它。
或者,我在这里遗漏了什么?