如何在JNI环境的本机端正确同步线程?

Lod*_*ijk 10 c++ java java-native-interface multithreading synchronization

问题简介

我通过JNI在一个进程中使用C++和Java.对于有问题的用例,C++线程和Java线程都访问相同的数据,他们在C++端这样做,我想正确地同步访问.

到目前为止,几乎所有的JNI线程同步都在Java端,答案显而易见:使用提供的Java并发包和内置的并发语言功能.不幸的是,答案在C++方面并不那么明显.

我到目前为止所尝试的内容

我尝试使用pthreads互斥锁认为它可能会工作,即使我没有使用pthreads来创建线程,但是在尝试锁定时偶尔会卡住 - 我将在下面展示更远的示例.

问题详情

在我目前的具体用法中,c ++正在轮询Java在1秒计时器上提供的更改(不是我想要的,但我不知道如果考虑到遗留c ++代码的性质,我将如何使其成为事件驱动的).Java线程通过调用本机函数来提供数据,c ++将数据复制到c ++结构中.

这是代码中的情况类型(发生在2个线程,Thread1和Thread2):

代码示例

请注意SSCCE,因为它缺少TheData和的定义TheDataWrapper,但它们包含的内容并不重要.假设它们只包含几个公共ints,如果这有助于你的思考过程(虽然,在我的情况下,它实际上是多个int数组和float数组).

C++:

class objectA
{
    void poll();
    void supplyData(JNIEnv* jni, jobject jthis, jobject data);
    TheDataWrapper cpp_data;
    bool isUpdated;

    void doStuff(TheDataWrapper* data);
};

// poll() happens on a c++ thread we will call Thread1
void objectA :: poll()
{
    // Here, both isUpdated and cpp_data need synchronization

    if(isUpdated)
    {
        do_stuff(&cpp_data);
        isUpdated = false;
    }
}

// supplyData happens on the Thread2, called as a native function from a java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    // some operation happens that copies the java data into a c++ equivalent
    // in my specific case this happens to be copying ints/floats from java arrays to c++ arrays
    // this needs to be synchronized
    cpp_data.copyFrom(data);
    isUpdated = true;
}
Run Code Online (Sandbox Code Playgroud)

Java的:

class ObjectB
{
    // f() happens on a Java thread which we will call Thread2
    public void f()
    {
        // for the general case it doesn't really matter what the data is
        TheData data = TheData.prepareData();
        supplyData(data);
    }

    public native void supplyData(TheData data);
}
Run Code Online (Sandbox Code Playgroud)

我到目前为止所尝试的细节

当我尝试下面的pthread锁定时,有时候执行会陷入困境pthread_mutex_lock.在这种情况下不应该存在死锁,但是为了进一步测试我运行了一个supplyData根本没有被调用的场景(没有提供数据),所以不应该有死锁,但第一次调用poll偶尔会挂起无论如何.在这种情况下,也许使用pthreads互斥锁不是一个好主意?或许我做了一些愚蠢的事情并继续忽视它.

到目前为止,我尝试使用pthreads如下:

代码示例

C++:

class objectA
{
    pthread_mutex_t dataMutex;
    ... // everything else mentioned before
}

// called on c++ thread
void objectA :: poll()
{
    pthread_mutex_lock(&dataMutex);

    ... // all the poll stuff from before

    pthread_mutex_unlock(&dataMutex);
}

// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    pthread_mutex_lock(&dataMutex);

    ... // all the supplyData stuff from before

    pthread_mutex_unlock(&dataMutex);
}
Run Code Online (Sandbox Code Playgroud)

我想到的另一种选择但尚未完成

我还考虑过使用JNI回调java来使用java的并发控制来请求锁定.这应该工作,因为任何一个线程都应该根据需要在java端阻塞.但是,由于从c ++访问java过于冗长,我希望避免经历这种麻烦.我可能会创建一个c ++类,它将JNI调用封装到java中以请求java锁; 这会简化c ++代码,虽然我想知道只是为了线程锁而在JNI上来回交叉的开销.

根据@Radiodef的评论,这似乎没有必要.似乎JNI包含MonitorEnter/ MonitorExit已经处理了c ++端锁定的函数.在java端使用这些传统锁时,有一些陷阱,所以在使用前阅读.我将尝试这一点,我希望MonitorEnter/ MonitorExit将成为答案,我建议@Radiodef从评论中做出回答.

闭幕

我怎么能正确同步这个?pthread_mutex_(un)锁定应该工作吗?如果没有,我可以使用什么来在C++线程和Java线程之间进行同步?

这里没有提供特定于JNI的C++代码,因为JNI桥正在工作,我可以来回传递数据.问题是具体关于正确通信的c ++/java线程之间的正确同步.

如前所述,我宁愿避免使用投票方案,但这可能最终成为另一个问题.遗留的c ++代码在X/motif中显示其用户界面的一部分,如果我没记错,上面的c ++线程碰巧是用于显示的事件线程.一旦插入了这个类的java用户界面,java线程将最终成为java事件调度线程,尽管现在java线程是一个自动测试线程; 无论哪种方式,它都是一个单独的java线程.

C++线程附加到JVM.实际上,这是创建JVM的C++线程,因此默认情况下应该附加它.

我已经成功地将其他Java用户界面元素插入到该程序中,但这是C++第一次需要来自Java的需要同步的非原子数据.是否有普遍接受的正确方法来做到这一点?

Lod*_*ijk 4

JNIEnv如果两个线程都连接到 JVM,那么您可以通过和函数访问 JNI 的MonitorEnter(jobject)同步MonitorExit(jobject)。顾名思义,MonitorEnter获取提供的锁jobject,并MonitorExit释放提供的锁jobject

\n\n

注意:有一些陷阱需要注意!MonitorEnter请注意\ 的描述的倒数第二段和MonitorExit\ 的描述的最后一段关于将MonitorEnter/MonitorExit与您可能认为兼容的其他类似机制混合和匹配的内容。

\n\n

这里

\n\n
\n

监控输入

\n\n

jint MonitorEnter(JNIEnv *env, jobject obj);

\n\n

输入与 obj 引用的底层 Java 对象关联的监视器。输入与 obj 引用的对象关联的监视器。obj 引用不能为 NULL。每个 Java 对象都有一个与其关联的监视器。如果当前线程已经拥有与 obj 关联的监视器,则会增加监视器中的计数器,以指示该线程进入监视器的次数。如果与 obj 关联的监视器不属于任何线程,则当前线程将成为该监视器的所有者,并将该监视器的条目计数设置为 1。如果另一个线程已拥有与 obj 关联的监视器obj,当前线程等待直到监视器被释放,然后再次尝试获取所有权。

\n\n

通过MonitorEnter JNI 函数调用进入的监视器无法使用monitorexit Java 虚拟机指令或同步方法返回退出。MonitorEnter JNI 函数调用和monitorenter Java 虚拟机指令可能会竞相进入与同一对象关联的监视器。

\n\n

为了避免死锁,通过 MonitorEnter JNI 函数调用进入的监视器必须使用 MonitorExit JNI 调用退出,除非 DetachCurrentThread 调用用于隐式释放 JNI 监视器。

\n\n

链接

\n\n

JNIEnv接口函数表中的索引217。

\n\n

参数

\n\n

env:JNI 接口指针。

\n\n

obj:普通的 Java 对象或类对象。

\n\n

返回

\n\n

成功则返回\xe2\x80\x9c0\xe2\x80\x9d;失败时返回负值。

\n
\n\n

\n\n
\n

监控退出

\n\n

jint MonitorExit(JNIEnv *env, jobject obj);

\n\n

当前线程必须是与 obj 引用的底层 Java 对象关联的监视器的所有者。线程递减指示其进入此监视器的次数的计数器。如果计数器的值变为零,则当前线程释放监视器。

\n\n

本机代码不得使用 MonitorExit 退出通过同步方法或监视器输入 Java 虚拟机指令输入的监视器。

\n\n

链接

\n\n

JNIEnv接口函数表中的索引218。

\n\n

参数

\n\n

env:JNI 接口指针。

\n\n

obj:普通的 Java 对象或类对象。

\n\n

返回

\n\n

成功则返回\xe2\x80\x9c0\xe2\x80\x9d;失败时返回负值。

\n\n

例外情况

\n\n

IllegalMonitorStateException: 如果当前线程不拥有监视器。\n

\n
\n\n

因此,问题中尝试使用 pthreads 的 C++ 代码应更改如下(代码假设JNIEnv*指针是预先以典型的 JNI 方式获取的):

\n\n
class objectA\n{\n    jobject dataMutex;\n    ... // everything else mentioned before\n}\n\n// called on c++ thread\nvoid objectA :: poll()\n{\n    // You will need to aquire jniEnv pointer somehow just as usual for JNI\n    jniEnv->MonitorEnter(dataMutex);\n\n    ... // all the poll stuff from before\n\n    jniEnv->MonitorExit(dataMutex);\n}\n\n// called on java thread\nvoid objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)\n{\n    // You will need to aquire jniEnv pointer somehow just as usual for JNI\n    jniEnv->MonitorEnter(dataMutex);\n\n    ... // all the supplyData stuff from before\n\n    jniEnv->MonitorExit(dataMutex);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

感谢@Radiodef 提供了答案。不幸的是,这是作为评论。我等到第二天下午才给Radiodef留出时间来答复,所以现在我就这么做了。感谢 Radiodef 为我提供了修复此问题所需的帮助。

\n