3

假设我的游戏应用程序中有两个线程(除了主线程):

  • GLRenderer 线程(由 Android 提供GLSurfaceView.Renderer
  • 另一个线程(游戏线程)

两个线程都使用 JNI 调用应用程序的某些 C++(即 Android NDK)组件。

假设我IntBuffer在 Java 中直接分配(例如从 GLRenderer 线程,但不要假设这个)。事实:

  • 此直接缓冲区由 GLRenderer 线程中的本机代码读取(即由通过 JNI 调用的 C++ 组件)
  • 这个直接缓冲区有时从另一个线程(游戏线程)写入的

在以下两种情况下,同步(实际上是确保数据可见性)的(最佳)方式是什么,即保证 GLRenderer 代码中的本机代码看到最新的IntBuffer内容?

  • 场景 #1:游戏线程的 Java 代码写入IntBuffer(例如 via IntBuffer.put()
  • 场景#2:从游戏线程调用的本机代码写入IntBuffer

我在想标准的 Java 同步将适用于这两种情况:

public void onDrawFrame(GL10 gl) { // the GLRenderer thread
    // ...
    synchronized (obj) {
        callNativeCode1(); // a JNI call; this is where the C++ native code reads the IntBuffer
    }

}

public void run() { // the game thread
    // ...

    synchronized (obj) {
        intBuffer.put(...); // writing the buffer from managed code
    }

    // ...
    synchronized (obj) {
        callNativeCode2(); // a JNI call; writing the buffer from C++ native code
    }
}
4

3 回答 3

2

No knowledge of the specifics of memory sharing with JNI but I would suggest using an AtomicIntegerArray.

Your options are:

  1. synchronized - You would need to somehow achieve the same in the JNI - not easy.
  2. volatile - making a whole array volatile would be a problem.
  3. atomics - I'd say the best route.

See Package java.util.concurrent.atomic for:

The AtomicIntegerArray, AtomicLongArray, and AtomicReferenceArray classes further extend atomic operation support to arrays of these types. These classes are also notable in providing volatile access semantics for their array elements, which is not supported for ordinary arrays.

This would essentially ensure that so long as the JNI code does not do anything to bypass the cache flush semantics of Java then the JNI package should see a consistent and updated view of the data.

I would recommend some significant research to confirm this but I believe this is the only way you will achieve what you are looking for.

于 2013-10-30T12:08:40.870 回答
1

如果我正确理解您的问题,这意味着您要确保callNativeCode1()(read) 和callNativeCode2()(write) 不会同时发生,并且如果这两种方法都是本机的,那么围绕它们进行同步将在 JNI 层中无效。

一个选项是创建一个为读/写操作调用的 java 方法并同步它

public void synchronized readOrWrite(boolean read){
    if (read){
        callNativeCode1();
    }
    else {
        callNativeCode2();
    }
}

所以你的代码是:

    public void onDrawFrame(GL10 gl) { // the GLRenderer thread
        readOrWrite(true);
    }

    public void run() { // the game thread

    synchronized (obj) {
        intBuffer.put(...); // writing the buffer from managed code
    }

    readOrWrite(false);
于 2013-11-01T14:02:26.030 回答
1

您可以使用 JNI 等效的同步来保证两个线程访问的本机数据是同步的:

//before read/writing shared data
(*env)->MonitorEnter(obj);

...                      /* synchronized block */

//after read/writing shared data
(*env)->MonitorExit(obj);

您可以在这篇IBM 文章中找到更多信息。

编辑: 经过更多的挖掘,事实证明 C 代码应该如何保持同步取决于 VM 实现(参见第 2.1 节:http ://www.hdfgroup.org/hdf-java-html/JNI/ )。可怕的部分是这样的:

...在大多数情况下,无法知道 C 代码必须做什么。

不幸的是,我找不到有关 Android 如何专门处理这个问题的信息,或者它是否在 Android 版本之间得到一致处理。

然而,另一个有趣的信息是由 Android 开发网站 ( http://developer.android.com/training/articles/smp.html ) 提供的,它表明 ARM CPU 提供了弱内存一致性,因此这可能是 C 代码的默认行为.

基本上问题归结为:在 Java 中同步线程访问时是否会同步 c 数据。由于链接 1 ( http://www.hdfgroup.org/hdf-java-html/JNI/ ) 没有给我们一个直接的答案,链接 2 ( http://developer.android.com/training/ articles/smp.html ) 表明 ARM CPU 提供了较弱的内存一致性,再次在 C 代码中执行监视器调用似乎是更安全的选择。

于 2013-11-01T13:41:50.613 回答