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

 2023-01-21    278  

问题描述

问题简介

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

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

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

我到目前为止尝试过的简短

我尝试使用pthreads mutex以为即使我不使用pthreads来创建线程,它也可能起作用,但是当试图锁定时偶尔会卡住 – 我会显示下面的一个示例.

问题详细信息

在我当前的特定用法中,C ++正在对Java在1秒的计时器上提供的更改进行轮询(不是我想要的,但我不确定如何将其作为事件驱动传统C ++代码). Java线程通过调用本机函数来提供数据,C ++将数据复制为C ++结构.

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

代码示例

注意一个SSCCE,因为它缺少TheData和TheDataWrapper的定义,但这并不重要.假设它们仅包含几个公共int,如果这有助于您的思考过程(尽管在我的情况下,它实际上是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;
}

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);
}

我到目前为止尝试的详细信息

当我尝试按照下面的pthread锁定时,有时执行会卡在pthread_mutex_lock中.在这种情况下,不应该发生僵局,但要进一步测试,我遇到了一个根本没有被调用的场景(没有提供数据),因此不可能进行僵局,但是第一个呼吁poll偶尔会悬挂.在这种情况下,也许使用pthreads mutex并不是一个好主意?也许我做了一些愚蠢的事情,并不断忽视它.

到目前为止,我尝试使用以下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);
}

我想到但没有做过的另一种选择

我还考虑使用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桥在工作,因此我可以来回传递数据,因此在这里没有提供JNI特定的C ++代码.这个问题是关于否则正确通信的C ++/Java线程之间的正确同步.

如前所述,我希望避免进行投票方案,但这可能最终成为另一个问题.旧版C ++代码以X/图案显示其用户界面的一部分,如果我没记错的话,上面的C ++线程恰好是用于显示的事件线程.一旦插入了此类的Java用户界面,Java事件Dispatch线程最终将成为Java事件Dispatch线程,尽管现在Java线程是自动化的测试线程.无论哪种方式,这是一个单独的Java线程.

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

我已经成功地将其他Java用户界面元素插入此程序,但这是C ++首次需要从Java中进行非原子数据,这需要同步.是否有一种公认的正确方法来执行此操作?

推荐答案

如果两个线程都连接到JVM,则可以通过MonitorEnter(jobject)’s MonitorEnter(jobject)和MonitorExit(jobject)函数访问JNI的同步.就像听起来一样,MonitorEnter aquiriagir在提供的jobject上锁定,MonitorExit释放了提供的jobject上的锁.

注意:有一些陷阱要注意!请注意MonitorEnter的描述的第二到最后一段以及MonitorExit的最后一段关于混合和匹配MonitorEnter/MonitorExit与其他类似机制的描述,您可能会认为这些机制是兼容的.

请参阅在这里

Monitorenter

Jint Monitorenter(Jnienv *env,jobignt obj);

输入与基础Java对象相关的显示器
由OBJ.输入与所指对象关联的监视器
由OBJ. OBJ参考不得无效.每个Java对象都有一个
与之关联的监视器.如果当前线程已经拥有
与OBJ关联的监视器,它会增加监视器中的计数器
指示该线程输入监视器的次数.如果
与OBJ关联的监视器不属于任何线程,
当前线程成为监视器的所有者,设置条目
该监视器的计数为1.如果另一个线程已经拥有监视器
与OBJ关联,当前线程等待直到监视器为
发布,然后再次尝试获得所有权.

通过Monitorenter JNI函数调用输入的监视器不能为
使用monitorexit Java虚拟机指令或
同步方法返回. Monitorenter JNI功能调用和A
Monitorenter Java虚拟机指令可能会参加
与同一对象关联的监视器.

为避免僵局,监视器通过Monitorenter JNI输入
必须使用monitorexit jni调用退出函数调用,除非
dixacrentThread调用用于隐式发布JNI
监视器.

链接:

jnienv接口函数表中的索引217.

参数:

env:JNI接口指针.

obj:普通的java对象或类对象.

返回:

成功返回” 0″;返回失败的负值.

monitorexit

jint monitorexit(jnienv *env,jobject obj);

当前线程必须是关联的监视器的所有者
OBJ提到的基础Java对象.线程减小
表示输入此的次数的计数器
监视器.如果计数器的值变为零,则当前线程
释放监视器.

本机代码不得使用monitorexit退出通过
同步方法或监视器Java虚拟机
指示.

链接:

jnienv接口函数表中的索引218.

参数:

env:JNI接口指针.

obj:普通的java对象或类对象.

返回:

成功返回” 0″;返回失败的负值.

例外:

IllegalMonitorStateException:如果当前线程不拥有
监视器.

因此,应如下更改试图使用Pthreads的问题中的C ++代码(代码假设JNIEnv*指针以典型的JNI方式以某种方式获取了以某种方式获取):

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

// called on c++ thread
void objectA :: poll()
{
    // You will need to aquire jniEnv pointer somehow just as usual for JNI
    jniEnv->MonitorEnter(dataMutex);

    ... // all the poll stuff from before

    jniEnv->MonitorExit(dataMutex);
}

// called on java thread
void objectA :: supplyData(JNIEnv* jni, jobject jthis, jobject data)
{
    // You will need to aquire jniEnv pointer somehow just as usual for JNI
    jniEnv->MonitorEnter(dataMutex);

    ... // all the supplyData stuff from before

    jniEnv->MonitorExit(dataMutex);
}

@radiodef提供了答案.不幸的是,这是评论.我等到第二天下午,让Radiodef有时间做出答案,所以现在我要这样做了.谢谢Radiodef提供了我需要修复的轻推.

其他推荐答案

如果您在本机线程和Java线程之间进行同步,则使用本机静音和Java监视器可能是谨慎的.
另外,如果您有可用的话,我建议使用std :: mutex在本机线程中建立同步. std :: lock_guard也很有用,并且为具有.lock()和.unlock()方法的Java监视器创建一些包装器,因此您可以使用std :: lock_guard使用它们也会有所帮助(那么您可以让C ++让C ++编译器完成工作).
我说您应该使用两者的主要原因是因为Monitorenter并不完美,它容易竞争条件.具体而言,据我从JNI文档中所知,它无法建立同步( jni文档).
本机STD :: mutex.lock()的使用将同步与本机解锁.

#include <mutex>
jobject magicObtainLockObject();
JNIEnv* magicObtainJNIEnv();

struct compound_lock{
private:
std::mutex mtx;
public:
void lock(){
mtx.lock();
magicObtainJNIEnv()->MonitorEnter(magicObtainLockObject());
}
void unlock(){
magicObtainJNIEnv()->MonitorExit(magicObtainLockObject());
mtx.unlock();
}
};

struct objectA{
...
compound_lock lock;
};

void objectA::poll(){
std::lock_guard<compound_lock> sync{lock};
...
}

void objectA::supplyData(JNIEnv* jni, jobject jthis, jobject data){
std::lock_guard<compound_lock> sync{lock};
...
}

以上所述是小编给大家介绍的您如何在JNI环境的本机侧正确同步线程?,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对77isp云服务器技术网的支持!

原文链接:https://77isp.com/post/26271.html

=========================================

https://77isp.com/ 为 “云服务器技术网” 唯一官方服务平台,请勿相信其他任何渠道。