5

我有一些 Java 代码正在调用一些本机代码,这些代码最初是用 Fortran 编写的,使用 JNA。(它是一个数值库,许多数学家在 Fortran 中进行编码。)它被编译成一个.so库,见下文:

在我的代码中对所有单元进行了测试,我得到了很好的结果,但后来我尝试使用来自多个线程的代码,一切都开始失败并出现奇怪的错误。然后我研究了一些关于可重入 Fortran 代码的东西,并意识到我使用的库相当于一些全局变量(SAVEFortran 中的关键字,它在再次调用函数时记住变量的值:fortran SAVE 语句

现在,我正在将对库的调用打包成synchronized块,但这会显着降低性能。在我看来,将库重新设计为可重入需要付出巨大的努力(它有几千行数字代码,并且不清楚在运行子例程时这些值如何传递。)有谁知道解决问题的最佳方法是什么?我的想象表明...

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

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

4

2 回答 2

2

作为参考,我只想分享我为此实现的以下类。它采用给定的库和接口,制作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

于 2013-09-26T22:38:52.790 回答
1

我不确定每个线程都有一个单独的库实例,但这是我多年前所做的:让操作系统为您重新进入。

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

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

于 2013-01-28T03:46:54.467 回答