当前位置: 首页 > 知识库问答 >
问题:

从多个Java线程调用不可重入的本机共享库

邢凯歌
2023-03-14

我有一些Java代码,它使用JNA调用一些本机代码,这些代码最初是用Fortran编写的。(这是一个数字库,许多数学工作者用Fortran编写代码。)它被编译成。因此库,请参见以下内容:

  • Fortran:https://github.com/mizzao/libmao/tree/master/src/main/fortran

在代码中进行单元测试时,我得到了很好的结果,但后来我尝试使用多线程的代码,结果一切都失败了,出现了奇怪的错误。然后我研究了一些关于可重入Fortran代码的内容,发现我使用的库相当于一些全局变量(Fortran中的SAVEkeywords,在再次调用函数时记住变量的值:Fortran SAVE语句)

目前,我正在将对库的调用包装在同步的块中,但这大大阻碍了性能。在我看来,重新设计可重入库需要付出很大的努力(它有几千行数字代码,并且不清楚子程序运行时这些值是如何传递的)。)有人知道解决这个问题的最好方法吗?我的想象表明...

  • 有没有办法让每个Java线程在内存中加载共享库的单独副本,以便全局变量有效地是线程本地的?这可能吗?我不确定JNA的直接绑定或库绑定是如何工作的,以及是否有这样使用它的方法。
  • 即使从不同的VM调用,它仍然会被拧吗?我怎么检查才能确定?
  • 有没有办法让gfortrangcc)以可重入的方式编译Fortran代码?
  • 是否有一些快速和肮脏的方法使Fortran代码可重入?我已经搜索了RECURSIVE关键字,它显然将变量保留在堆栈上,但这似乎与现有代码不兼容。
  • 还有其他可能的解决方案吗?

我确认多个VM的情况正常;这是有意义的,因为它们不共享内存。仍然是PITA,比线程更不方便。

共有2个答案

周意智
2023-03-14

我不确定每个线程是否都有一个单独的库实例,但是我在几年前做了一件事:让操作系统为您制作Reentrant。

我最终在Unix机器上创建了一个应用程序实例池,并使用网络套接字与它们通信——每个进程都在监听自己的套接字。

即使库不是可重入的,作为一个单独的过程启动它也可以...也许您可以在库周围编写一个薄的unix包装器,并通过您自己的专有协议进行通信。

易俊远
2023-03-14

作为参考,我只想与大家分享我为此实现的以下类。它获取给定的库和接口,制作n副本,并将JNA代理接口映射到每个副本,然后返回另一个实现线程安全锁定的代理接口,创建一个可重入的版本,该版本可以运行最多一个处理器的数量。

public class LibraryReplicator<C> {

    final BlockingQueue<C> libQueue;
    final Class<C> interfaceClass;
    final C proxiedInterface;

    @SuppressWarnings("unchecked")
    public LibraryReplicator(URL libraryResource, Class<C> interfaceClass, int copies) throws IOException {
        if (!interfaceClass.isInterface()) 
            throw new RuntimeException(interfaceClass + "is not a valid interface to map to the library.");

        libQueue = new LinkedBlockingQueue<C>(copies);
        this.interfaceClass = interfaceClass;

        // Create copies of the file and map them to interfaces
        String orig = libraryResource.getFile();
        File origFile = new File(orig);
        for( int i = 0; i < copies; i++ ) {
            File copy = new File(orig + "." + i);
            Files.copy(origFile, copy);                     

            C libCopy = (C) Native.loadLibrary(copy.getPath(), interfaceClass);         
            libQueue.offer(libCopy); // This should never fail
        }               

        proxiedInterface = (C) Proxy.newProxyInstance(
                interfaceClass.getClassLoader(), 
                new Class[] { interfaceClass }, 
                new BlockingInvocationHandler());
    }

    public LibraryReplicator(URL libraryResource, Class<C> interfaceClass) throws IOException {
        this(libraryResource, interfaceClass, Runtime.getRuntime().availableProcessors());
    }

    public C getProxiedInterface() {
        return proxiedInterface;
    }

    /*
     * Invocation handler that uses the queue to grab locks and maintain thread safety.  
     */
    private class BlockingInvocationHandler implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
            C instance = null;

            // Grab a copy of the library out of the queue          
            do {
                try { instance = libQueue.take(); }
                catch(InterruptedException e) {}
            } while(instance == null);

            // Invoke the method
            Object result = method.invoke(instance, args);

            // Return the library to the queue
            while(true) {
                try { libQueue.put(instance); break; }
                catch( InterruptedException e ) {} 
            } 

            return result;
        }       
    }

}

作为静态初始值设定项的一部分,示例用法如下:

MvnPackGenz lib = new LibraryReplicator<MvnPackGenz>(
        MvnPackGenz.class.getClassLoader().getResource("mvnpack.so"), 
        MvnPackGenz.class).getProxiedInterface();

这将创建库的一堆副本(在我的例子中是12个),创建lib变量,上面的“看起来”是可重新进入的,并且可以由多个线程安全运行:

-rw-r--r-- 1 mao mao 50525 Sep 26 13:55 mvnpack.so
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.0
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.1
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.10
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.11
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.2
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.3
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.4
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.5
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.6
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.7
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.8
-rw-r--r-- 1 mao mao 50525 Sep 26 18:21 mvnpack.so.9

您可以在以下位置看到更新版本:

https://github.com/mizzao/libmao/blob/master/src/main/java/net/andrewmao/misc/LibraryReplicator.java

 类似资料:
  • 背景:我正在并行运行自动化测试。使用pom.xml中的分叉,多个浏览器在相同数量的线程中启动,即1个浏览器是1个线程。 中的下面插件创建了与线程(fork)计数相等数量的。 所有这些类都同时并行执行。因此,似乎每当我创建或时,每个线程都会创建自己的这些,因此跨多个线程共享变量的概念是不起作用的。 我只想让一个线程访问“准备测试数据”函数,并将<code>标志 我正在按照教程https://www.

  • 问题内容: 我想像这样在多个线程之间共享一个变量: 我想在主线程和帮助线程之间共享,这是我创建的两个不同的Java类。有什么办法吗?谢谢! 问题答案: 二者并可以参照包含该变量的类。 然后,可以使该变量为 volatile ,这意味着 对该变量的更改在两个线程中立即可见。 有关更多信息,请参见本文。 易变变量 共享已同步的可见性功能,但不共享原子性功能。这意味着线程将自动 查看volatile变量

  • 我创建了一个可运行的类a,它为我执行一些任务。我使用ExecutorService提交这个类,以便并行执行这些任务。 可运行类A调用另一个对象B,该对象发送一个AsyncFuture请求(future.get()one)。 我将可运行类A的单独对象提交给ExecutorService,但是,类B的对象由bean(单例)引用。这会导致线程执行出现问题吗? 我注意到类A的一些对象没有被任何线程执行。

  • 问题内容: 在多个线程上调用Java 对象是否安全?输出会正确序列化吗? 澄清: 在我的情况下,类记录器拥有FileOutputStream引用,并且多个线程可以调用记录器写,从而格式化输出并调用FileOutputStream写。 我应该同步记录器的写方法以保证来自多个线程的消息不会混合吗? 问题答案: 不能多次打开文件,因此答案是否定的。 在看到您的编辑之后,是的,您应该在记录器中引入同步,以

  • 我注意到一些web框架(如Play Framework)允许您配置多个不同大小的线程池(线程池中的线程数)。假设我们在单核的单机中运行这个游戏。拥有多个线程池不会有很大的开销吗?