我有一些Java代码调用一些本机代码,最初是用Fortran编写的,使用JNA。 (这是一个数值库,许多数学人员在Fortran中编写代码。)它被编译为.so
库,见下文:
我在我的代码中测试了所有单元,但是我得到了很好的结果,但后来我尝试使用来自多个线程的代码,一切都开始失败并出现奇怪的错误。然后我研究了一些stuff about reentrant Fortran code并意识到我使用的库具有相当于Fortran中的一些全局变量(SAVE
关键字,它们在再次调用函数时记住变量的值:{{ 3}})
现在我正在synchronized
块中包装对库的调用,但这显着地影响了性能。在我看来,重新设计库可能需要付出很大的努力才能重入(它有几千行数字代码,并且不清楚当子程序运行时值如何延续。)有谁知道解决问题的最佳方法是什么?我的想象力表明......
gfortran
(gcc
)以可重入的方式编译Fortran代码?RECURSIVE
关键字,它显然将变量保存在堆栈中,但这似乎与现有代码不兼容。我确认多个虚拟机的状况良好;这是有道理的,因为他们不共享记忆。仍然是一个PITA,但比线程更不方便。
答案 0 :(得分: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
答案 1 :(得分:1)
我不确定每个线程都有一个单独的库实例,但是这就是我多年前所做的:让操作系统为你重入。
我最终在Unix机器上创建了一个应用程序实例池,并使用网络套接字与它们进行通信 - 每个进程都在自己的套接字上进行侦听。
即使库不可重入,也可以将其作为单独的进程启动。 。 。也许你可以在库周围编写一个瘦的unix包装器,并通过你自己的专有协议进行通信。