【问题标题】:JNI 8 C++ : Thread attach and detach And async callbackJNI 8 C++:线程附加和分离以及异步回调
【发布时间】:2020-12-10 10:23:09
【问题描述】:

如何从 std::thread 异步调用 Java 方法?

假设这是一个 IM bot sdk,因为它的逻辑基本上是一个 IM bot sdk。

最重要的是:如何异步调用java方法和回调native。

底部有逻辑流程,可能有帮助。

例如:

收到消息A“备份”,然后用MsgA调用java插件,插件处理这个事件需要10秒,并调用5次本地方法来满足它的需要。

同时,接收消息B“echo”,处理时间仅为10ms,并通过调用native方法发送消息。

所以,MsgB 在 MsgA 之后收到,但在 MsgA 之前完成。

如果使用纯 C C++ java 或其他,那将很容易实现。但是这里我发现了一个让人头疼的问题:JNI thread Attach。

※ 第一个问题:Wired JNI attach

我已阅读文档找到答案,他们都没有工作,我的情况与每个人都不同

我正在使用 Zulu JDK8 (zulu8.48.0.53-ca-fx-jdk8.0.265-win_x64) 和 MinGW64 C++,用于演示:

public class ThreadTest {

    private static int count = 0;

    private static final Random random = new Random();

    public static int count() {
        try {
            Thread.sleep(random.nextInt(2000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return count++;
    }
    
}

这是 C++ 中的工作函数

void worker(JNIEnv* localEnv) {
    jclass clazz = localEnv->FindClass("ThreadTest");
    jmethodID method = localEnv->GetStaticMethodID(clazz, "count", "()I");
    jchar result = localEnv->CallStaticCharMethod(clazz, method);
    int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());
    printf("[Worker Done] %d =>> %d\n", tid, result);
}

如果没有附加,我们会得到,这是预期的:

worker(env);
// Here the first call from main thread, Working find;
// [Worker Done] -1444639049 =>> 0

jvm->DetachCurrentThread();

std::thread t1(worker, env);
t1.join();

// Process crashed because not attach jni
// Process finished with exit code -1073741819 (0xC0000005)

并为t1添加tWorker函数:

void tWorker (JavaVM* gJvm) {

    int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());

    printf("[Thread Run] %d\n", tid);

    JavaVMAttachArgs* args;
    args->version = JNI_VERSION_1_8;
    args->name = nullptr;
    args->group = nullptr;

    JNIEnv* lEnv;

    printf("[Attach for] %d\n", tid);
    int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args);
    printf("[Attach Done] %d =>> %d\n", tid, attachResult);
    delete args;

    worker(lEnv);

    gJvm->DetachCurrentThread();

}

我知道了:

[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944

Process finished with exit code -1073741819 (0xC0000005)

有些回答说你应该使用GetEnv

void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {

    int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());

    printf("[Thread Run] %d\n", tid);

    JavaVMAttachArgs* args;
    args->version = JNI_VERSION_1_8;
    args->name = nullptr;
    args->group = nullptr;

    JNIEnv* lEnv;

    printf("[GetEnv for] %d\n", tid);
    int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), JNI_VERSION_1_8);
    printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);

    printf("[Attach for] %d\n", tid);
    int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(lEnv), &args);
    printf("[Attach Done] %d =>> %d\n", tid, attachResult);

    delete args;

    worker(gEnv);

    gJvm->DetachCurrentThread();

}

得到相同的结果:

[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944

Process finished with exit code -1073741819 (0xC0000005)

关于我发现的更多帖子,将本地替换为全局(这对逻辑和文档没有任何意义,但在他们的问题中解决了)

    //JNIEnv* lEnv;

    printf("[GetEnv for] %d\n", tid);
    int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(gEnv), JNI_VERSION_1_8);
    printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);

    printf("[Attach for] %d\n", tid);
    int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(gEnv), &args);
    printf("[Attach Done] %d =>> %d\n", tid, attachResult);

那没用,即使我尝试了所有 16 种组合,也对我不起作用。

[Worker Done] -1444639049 =>> 0
[Thread Run] 1709724944

Process finished with exit code -1073741819 (0xC0000005)

问题一:里面发生了什么?

※第二个问题:如何实现:

更新 1:

问题 1 已解决。

void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {

    int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());

    printf("[Thread Run] %d\n", tid);

    auto* args = new JavaVMAttachArgs{};
    args->version = JNI_VERSION_1_8;
    args->name = nullptr;
    args->group = nullptr;

    JNIEnv* lEnv;

    printf("[GetEnv for] %d\n", tid);
    int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(&args, JNI_VERSION_1_8);
    printf("[GetEnv Done] %d =>> %d\n", tid, getEnvResult);

    if (getEnvResult == JNI_EDETACHED) {
        printf("[Attach for] %d\n", tid);
        int attachResult = gJvm->AttachCurrentThread(reinterpret_cast<void**>(&lEnv), &args);
        printf("[Attach Done] %d =>> %d\n", tid, attachResult);
    }

    delete args;

    worker(gEnv);

    gJvm->DetachCurrentThread();
}

没有强制转换会导致编译错误 error: invalid conversion from 'JNIEnv**' {aka 'JNIEnv_**'} to 'void**' [-fpermissive]

【问题讨论】:

  • 关于GetEnv:您应该检查它返回的内容,并且只有当它返回JNI_EDETACHED 时,您才应该继续调用AttachCurrentThread。请注意,这是针对您不知道线程是否已附加的情况。如果您 100% 确定该线程尚未附加,那么您可以跳过 GetEnv。但是,为每个回调执行 Attach 和 Detach 可能有点浪费,具体取决于这些回调发生的频率。最好将每个线程附加一次,并在线程销毁时将它们分离。
  • @Michael 问题 1 已解决,但作为进一步的测试,多线程 AttachCurrentThread 将导致退出,因为线程 B 在线程 A 掠夺环境。所以 It might be better to attach each thread once, and detach them when the thread is destroyed. 我不知道那个意思。 (part1/2)
  • 基于该模块的空洞项目,因为我见过类似的 SDK:事件发生然后调用 C++ 线程,然后 JNI 调用 java 方法,作为插件开发人员,每个事件都在隔离线程中工作,一个崩溃不会影响其他人。而且java可能需要很长时间,响应和回调native可能是无序的,那么在JNI中这是不可能的吗?(第2/2部分)

标签: java c++ multithreading asynchronous java-native-interface


【解决方案1】:

看起来您的问题不在于 JVM 的使用,而在于 C++ 代码。看这段代码:

void tWorker02(JavaVM* gJvm, JNIEnv* gEnv) {

    int tid = std::hash<std::thread::id>{}(std::this_thread::get_id());

    printf("[Thread Run] %d\n", tid);

    JavaVMAttachArgs* args;
    args->version = JNI_VERSION_1_8;
    args->name = nullptr;
    args->group = nullptr;

注意这里:

    JavaVMAttachArgs* args;
    args->version = JNI_VERSION_1_8;

您的 args 是一个指针,未初始化。它调用未定义的行为,最有可能崩溃。
您还试图在未初始化的情况下删除它:

    delete args;

另外我也看不懂这段代码:

    JNIEnv* lEnv;
    ...
    int getEnvResult = gJvm->GetEnv(reinterpret_cast<void**>(lEnv), ...

这里的reinterpret_cast是什么意思?根据函数的定义,需要一个指向指针的指针,而不是强制类型转换:

    JNIEnv* lEnv;
    ...
    int getEnvResult = gJvm->GetEnv(&lEnv, ...

好的,你可以转换它,但是你应该在这里传递指向指针的指针,所以转换一个指向指针static_cast&lt;void**&gt;(&amp;lEnv)的指针,但这可能不是必需的。

【讨论】:

    猜你喜欢
    • 2010-12-29
    • 2015-09-11
    • 1970-01-01
    • 1970-01-01
    • 2012-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多