【问题标题】:What is the most efficient implementation of a java like object monitor in C++?在 C++ 中,类 java 对象监视器的最有效实现是什么?
【发布时间】:2009-11-18 00:43:39
【问题描述】:

在 Java 中,每个对象都有一个同步监视器。所以我想这个实现在内存使用方面非常简洁,希望也很快。

当将它移植到 C++ 时,最好的实现是什么。我认为一定有比“pthread_mutex_init”更好的东西,还是java中的对象开销真的这么高?

编辑:我刚刚检查了 Linux i386 上的 pthread_mutex_t 是 24 字节大。如果我必须为每个对象保留这个空间,那就太棒了。

【问题讨论】:

  • 您可能想要指定您正在使用的平台,因为 C++ 中的线程必须是特定于平台的。使用 pthread 的解决方案将不同于使用 Win32 同步函数的解决方案。
  • 所有主要平台 - MacOSX、Solaris、Windows、Linux。

标签: c++ multithreading synchronized


【解决方案1】:

实际上,从某种意义上说,它比pthread_mutex_init 更糟糕。由于 Java 的等待/通知,您需要一个配对的互斥锁和条件变量来实现监视器。

在实践中,在实现 JVM 时,您会寻找并应用书中每一个特定于平台的优化,然后发明一些新的优化,以尽可能快地制作监视器。如果你不能做一个非常糟糕的工作,那么你肯定不会优化垃圾收集;-)

一个观察是不是每个对象都需要有自己的监视器。当前未同步的对象不需要同步。因此 JVM 可以创建一个监视器池,每个对象可以只有一个指针字段,当线程实际想要在对象上同步时填充该字段(例如,使用特定于平台的原子比较和交换操作)。因此监视器初始化的成本不必增加对象创建的成本。假设内存是预先清除的,对象创建可以是:递减一个指针(加上某种边界检查,预测错误分支到运行 gc 的代码等等);填写类型;调用最派生的构造函数。我认为你可以安排 Object 的构造函数什么都不做,但显然很大程度上取决于实现。

实际上,一般的 Java 应用程序在任何时候都不会同步很多对象,因此监视器池可能是时间和内存方面的巨大优化。

【讨论】:

  • 除非你有很多糟糕的程序员代码,它们会为每个方法添加同步。但我喜欢你的想法。交易哈希表查找以查找关联对象与内存开销。
  • 在我(相当老的)实现 JVM 的经验中,糟糕的编码人员还保证他们的程序创建的大多数对象都是字符串。因此,即使它们同步所有自己的方法,并以某种方式避免死锁,它仍然只是需要监视器的所有对象中的一小部分;-)。我不认为我们的 JVM 使用了 brianegge 链接中的瘦锁定:一个对象要么有监视器,要么没有。当时我们比 Sun 快(对于 PJava/J2ME),但那是在 HotSpot 的聪明东西进入移动设备之前,KVM 变得可怕。
【解决方案2】:

The Sun Hotspot JVM implements thin locks using compare and swap。如果对象被锁定,则等待线程等待锁定对象的线程的监视器。这意味着每个线程只需要一个重锁。

【讨论】:

  • 这就是我们喜欢的。所有指针都应该将低三位重新用于标志;-)
  • 这意味着类必须维护一个线程列表才能唤醒,然后在调用 notify 时触发每个线程。这个实现可以和任何系统一样快,但不是 pthreads。
【解决方案3】:

我不确定 Java 是如何做到的,但 .NET 不会直接在对象中保留互斥锁(或模拟 - 保存它的结构称为“syncblk”)。相反,它有一个全局同步块表,对象通过该表中的索引引用它的同步块。此外,对象不会在创建后立即获得同步块 - 相反,它是在第一次锁定时按需创建的。

我假设(注意,我不知道它实际上是如何做到的!)它使用原子比较和交换以线程安全的方式关联对象及其同步块:

  1. 检查我们对象的隐藏syncblk_index字段是否为0。如果不是0,则锁定并继续,否则...
  2. 在全局表中创建一个新的syncblk,获取它的索引(全局锁在此处根据需要获取/释放)。
  3. 比较和交换以将其写入对象本身。
  4. 如果之前的值为 0(假设 0 不是有效索引,并且是我们对象的隐藏 syncblk_index 字段的初始值),我们的 syncblk 创建没有争议。锁定它并继续。
  5. 如果之前的值不是 0,那么在我们创建我们的对象时,其他人已经创建了一个 syncblk 并将其与对象相关联,而我们现在有了该 syncblk 的索引。处理我们刚刚创建的那个,并锁定我们已经获得的那个。

因此,在最佳情况下,每个对象的开销为 4 字节(假设 syncblk 表中的 32 位索引),但对于实际已锁定的对象来说更大。如果您很少锁定对象,那么此方案看起来是减少资源使用的好方法。但是,如果您最终需要锁定大部分或所有对象,则直接在对象中存储互斥锁可能会更快。

【讨论】:

  • "如果你最终需要锁定大部分或所有对象" ...那么你不应该让你的程序快速运行;-)
  • 我应该更具体一些:当然我指的是特定类的对象,而不是一般的所有对象。通常,对于特定类的对象,可以肯定的是,在其生命的某个时刻,该类的任何实例都必须被锁定。如果是这样,直接在对象中分配互斥锁可能更有意义。
【解决方案4】:

您当然不需要为每个对象配备这样的监视器!

从 Java 移植到 C++ 时,我觉得盲目地复制所有内容是个坏主意。 Java 的最佳结构与 C++ 的最佳结构不同,尤其是因为 Java 具有垃圾收集功能而 C++ 没有。

只为那些真正需要它的对象添加一个监视器。如果只有某些类型的实例需要同步,那么创建一个包含同步所需的互斥锁(可能还有条件变量)的包装类并不难。正如其他人已经说过的那样,另一种方法是使用同步对象池,并通过某种方式为每个对象选择一个,例如使用对象地址的哈希来索引数组。

我会使用 boost 线程库或新的 C++0x 标准线程库来实现可移植性,而不是每次都依赖于平台细节。 Boost.Thread 支持 Linux、MacOSX、win32、Solaris、HP-UX 等。我的implementation of the C++0x thread library目前只支持Windows和Linux,其他的实现会在适当的时候推出。

【讨论】:

    猜你喜欢
    • 2011-08-22
    • 2011-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多