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

从多个Java线程调用不可重入的本机共享库,java,fortran,shared-libraries,jna,reentrancy,Java,Fortran,Shared Libraries,Jna,Reentrancy,我有一些Java代码,它使用JNA调用一些本机代码,这些代码最初是用Fortran编写的。(这是一个数字库,许多数学工作者用Fortran编写代码。)它被编译成。因此库,请参见以下内容: Fortran: Java绑定: 在代码中进行单元测试时,我得到了很好的结果,但后来我尝试使用多线程的代码,结果一切都失败了,出现了奇怪的错误。然后我查看了一些,发现我使用的库相当于一些全局变量(SAVEFortran中的关键字,它们在再次调用函数时会记住变量的值:) 目前,我正在synchronized块

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

  • Fortran:
  • Java绑定:
在代码中进行单元测试时,我得到了很好的结果,但后来我尝试使用多线程的代码,结果一切都失败了,出现了奇怪的错误。然后我查看了一些,发现我使用的库相当于一些全局变量(
SAVE
Fortran中的关键字,它们在再次调用函数时会记住变量的值:)

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

  • 是否有某种方法可以让每个Java线程在内存中加载共享库的单独副本,从而使全局变量实际上是线程本地变量?这可能吗?我不确定JNA的直接绑定或库绑定是如何工作的,以及是否有这样使用它的方法
  • 即使它是从不同的虚拟机调用的,它仍然会出错吗?我怎样才能确认呢
  • 有没有办法让
    gfortran
    gcc
    )以可重入的方式编译Fortran代码
  • 是否有一些快速而肮脏的方法使Fortran代码可重入?我搜索了
    RECURSIVE
    关键字,它显然在堆栈上保留了变量,但似乎与现有代码不兼容
  • 还有其他可能的解决办法吗

我确认多个虚拟机的情况正常;这是有道理的,因为它们不共享内存。不过,这仍然是一个PITA,而且比线程更不方便。

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

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


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

作为参考,我只想与大家分享我为此实现的以下类。它获取给定的库和接口,制作
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;
        }       
    }

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


如果您为您的库创建N个副本(使用不同的名称)并加载N次会怎么样?有点像“图书馆池”。每个线程都必须获取库的副本,并在完成后返回。对于获取和返回库,您仍然需要进行某种同步。可以插入RW围栏的东西。如果您的库加载了其他不安全的库,那么这将不起作用。@jdb,非常有创意的方法!现在,我在不同的虚拟机中运行令人尴尬的并行化代码,这几乎是一样的。让我们看看还会出现什么。如果库对系统资源的使用是其流程所独有的(例如,不总是使用名为“/tmp/output.txt”的文件),那么N个拷贝也可以工作(并且也可以与JNA一起工作(请参见),谢谢@technomage。刚刚开始实现它;请参见下面:)在github存储库的何处(或其他地方)文件是“mvnpack.so”吗?我正在尝试运行您的库,但如果没有该文件,则会出现错误。有一个
Makefile
,您可以在计算机上编译它:
MvnPackGenz lib = new LibraryReplicator<MvnPackGenz>(
        MvnPackGenz.class.getClassLoader().getResource("mvnpack.so"), 
        MvnPackGenz.class).getProxiedInterface();
-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