RAII 是否可以在不同步的情况下有效地在线程之间共享不可变对象



在无数关于C++式确定性销毁(RAII(与垃圾收集的优越性的争论中,前者的支持者经常认为它可以做垃圾收集可以做的一切。 但是,有一种模式在Java和.NET语言中经常使用,但我知道C++中没有很好的模式。 考虑类[可以是Java或C#;为简洁起见,使用公共字段}

public class MaxItemIdentifier
{
  public String maxItemName = null;
  public long maxItemValue = -0x7FFFFFFFFFFFFFFF-1;
  public void checkItem(String name, long value)
  {
    if (value > maxItemValue)
    {
      maxItemName = name;
      maxItemValue = value;
    }
  }
}

在 Java 和 C# 中,可以安全地传递在任何线程上创建的字符串。 此外,虽然该方法不是线程安全的,但即使线程使用不当也不会危及内存安全; maxItemName仍然可以保证null或标识传递给checkItem的字符串之一。 此外,该方法实际上永远不必复制(甚至查看(任何字符串的内容;它所作用的只是字符串引用。 由于引用已暴露给外界的字符串对象永远不会被修改,因此对字符串的引用可以被视为由此标识的字符序列的同义词,并且复制引用等效于复制文本。

有没有办法用C++或类似的基于 RAII 的语言编写一个等效的类,无论线程使用情况如何,都能保证内存安全,但在从单个线程运行时不会是不必要的低效? 我知道这种方法在C++中可行的唯一方法是:

  1. 每当遇到"值"大于前一个最大值的项时,该方法都会复制字符串的内容;这比简单地复制引用要慢。 此外,我不知道在线程使用不当的情况下,这可以保持内存安全的程度。

  2. 让该方法接收对引用计数指针的引用,并保存该类型的变量;当接收的值大于前一个最大值时,原子递
  3. 增接收指针上的引用计数,并以原子方式递减前一个最大项名称的引用计数;如果后者产生零,则释放该名称。 这种方法看起来很安全,但在许多平台上,原子递增和递减对于单线程使用来说过于昂贵,但对于多线程方案中的内存安全是必需的。

就个人而言,我相信一个好的语言/框架应该同时支持 RAII 和 GC,因为每个都可以非常容易和有效地处理一些其他根本无法处理的事情。 但是,在RAII中,可能会有一些我不熟悉的其他方法来处理此类事情。 使用 RAII 时,有没有办法使上述方法在单线程方案中使用时高效工作,但在对一个线程上创建的字符串的引用随后可能公开给其他线程的情况下也可以使用?

请注意,与其他一些多线程 RAII 方案不同,在这些方案中,对象具有可预测的生存期,在生产者线程中一致地创建并在使用者线程(相关帖子的主题(中销毁(,对不可变对象(如 Strings(的引用通常是共享的,没有任何引用持有者可识别为"所有者",并且没有任何方法知道任何特定的引用持有者是否或何时可能会覆盖对字符串的最后一个幸存引用。

这当然是一个众所周知的问题。

如果没有一些开销,就无法确定性地销毁共享对象。 在许多情况下,销毁出现在最终确定之上的原因是:

  1. 大多数对象不共享。
  2. 许多共享对象需要确定性销毁,并且在定稿之上提供这些对象的成本甚至高于使用 RAII 实现引用计数的成本。

显然,RAII在管理破坏方面做得很好。

它实际上只是在多个不同步用户之间共享的对象,并且将被放弃并且永远不会再次使用,因为最终确定不会破坏。 一个例子是零拷贝多播套接字(或至少 O(1( 拷贝(。

权衡仍然是在确定性(有一些计算开销(和非确定性之间。 由于 C 和 C++ 不强制实施单一的资源管理方法,因此实际上,将基于析构函数的确定性清理与高效的非确定性清理混合在一起比在 .NET 或 Java 运行时之上更容易,因为 .NET 或 Java 运行时的所有内容都经过非确定性释放。

本机世界中非确定性清理的一个例子是 Linux 内核中使用的 RCU 方法。 它是 C 代码,但同样适用于C++。

因此,即使在这里,RAII 也有优势,您只需对非确定性 RCU 使用一组不同的智能指针,而不是用于本地范围确定性发布或引用计数、线程同步和确定性发布的指针。

真的,这就是你的想法出错的地方。 RAII能够提供确定性寿命,但不限于确定性寿命。

最新更新