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

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

  • Fortran: https : //github.com/mizzao/libmao/tree/master/src/main/fortran
  • Java绑定: https : //github.com/mizzao/libmao/blob/master/src/main/java/net/andrewmao/probability/MvnPackDirect.java

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

现在我正在使用synchronized块包装对库的调用,但这显着地阻碍了性能。 在我看来,重新设计库是可重入的需要付出很大的努力(它有几千行数字代码,并且不清楚当子程序运行时值如何延续。)有谁知道解决问题的最佳方法是什么? 我的想象力表明……

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

我确认多个虚拟机可以正常运行; 这是有道理的,因为他们不共享记忆。 仍然是一个PITA,但比线程更不方便。

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

 public class LibraryReplicator { final BlockingQueue libQueue; final Class interfaceClass; final C proxiedInterface; @SuppressWarnings("unchecked") public LibraryReplicator(URL libraryResource, Class 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(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 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.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

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

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

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