Вызов трудоемкой задачи JNI в виде потока - PullRequest
4 голосов
/ 21 июля 2009

У меня сложная проблема с вызовом нативной функции с использованием JNI из потока.

Нативная функция - это устаревший код, который выполняет задачу, требующую больших вычислительных ресурсов. Поскольку я не хочу замораживать остальную часть программы, вычисления должны выполняться в фоновом потоке. EventBus используется для отправки результата расчета в основную программу.

В основном это должно быть довольно простым, что-то вроде этого:

public class CalculationEngine {
  private CalculationEngine(){}

  public static void calculateInBackground(final Parameters parameters) {

    new Thread(new Runnable() {
      public void run() {
        // Someone might change the parameters while our thread is running, so:
        final Parameters clonedParameters = parameters.clone();
        Results results = new Results();
        natCalc(clonedParameters, results);
        EventBus.publish("Results", results);
      }
    }).start();

  }

  public static void calculateNormally(final Parameters parameters) {
    Results results = new Results();
    natCalc(parameters, results);
    EventBus.publish("Results", results);
  }

  private static native synchronized void
    natCalc(Parameters parameters, Results results);      
}

Теперь метод calculateNormally, который блокирует основную программу, работает нормально, но метод calculateInBackground, который просто создает фоновый поток, чтобы выполнить то же самое, вызывает различные сбои в собственном коде , когда он вызывается последовательно . Под последовательностью я подразумеваю, что он вызывается снова только после того, как предыдущий поток закончил и вернул результат. Обратите внимание, что собственный код помечен synchronized, чтобы гарантировать, что одновременно может быть запущен только один его экземпляр.

Мой вопрос заключается в том, как может нативный код вести себя по-разному в зависимости от того, вызывается ли он из основного потока или из какого-либо другого потока? Это похоже на то, что нативный код сохранял «состояние» и не выходил из него, когда он вызывался из потока, отличного от основного потока. Есть ли способ «очистить» или «очистить» поток после его завершения? В JNI & Threads должно быть что-то, чего я просто не знаю.

Спасибо за любые подсказки!

Ответы [ 4 ]

7 голосов
/ 21 июля 2009

Я нашел рабочее решение, после поиска в Google и поиска фразы "Я обнаружил, что JNI очень глючит при вызове из отдельных потоков ... Поэтому убедитесь, что только один поток когда-либо вызывает ваш нативный код!" . Кажется, это правда; решение состоит в том, чтобы сохранить постоянный «многократно используемый» поток - я использовал Executors.newSingleThreadExecutor() - и вызывать нативный код только из этого потока. Это работает.

Таким образом, разница с точки зрения JNI была не между основным потоком и другим потоком, а в использовании различных потоков при последовательных вызовах. Обратите внимание, что в проблемном коде каждый раз создавался новый поток. Это должно работать таким образом, но это не так. (И нет, я не кеширую указатель JNIEnv.)

Было бы интересно узнать, является ли это ошибкой JNI, ошибкой в ​​нативном коде, чем-то во взаимодействии между ними и ОС или чем-то еще. Но иногда у вас просто нет возможности детально отладить более 10000 строк существующего кода, однако вы рады, что он заработал. Вот рабочая версия примера кода, давайте назовем это обходной путь :

public class CalculationEngine {
  private CalculationEngine(){}

  private static Parameters parameters;
  private static ExecutorService executor = Executors.newSingleThreadExecutor();

  private static Runnable analysis = new Runnable() {
      public synchronized void run() {
        Results results = new Results();
        natCalc(parameters, results);
        EventBus.publish("Results", results);
      }
  };  

  public static synchronized void
    calculateInBackground(final Parameters parameters) {
      CalculationEngine.parameters = parameters.clone();
      executor.submit(analysis);
  }

  private static native synchronized void
    natCalc(Parameters parameters, Results results);      
}
3 голосов
/ 22 июля 2009

Мой совет по использованию JNI - НЕ, если вы можете избежать этого. Скорее всего, это вызовет проблемы со стабильностью для вас. Вот несколько возможных альтернатив:

  1. Перекодировать нативную библиотеку в Java.
  2. Напишите команду оболочки для собственной библиотеки на C / C ++ / что угодно и запустите ее, используя java.lang.Process и friends
  3. Превратите нативную библиотеку в демона и получите доступ к ней с помощью сокетов.
1 голос
/ 04 сентября 2011

Здесь есть хорошая документация по этому поводу: Раздел 8.1 JNI и Threads. http://java.sun.com/docs/books/jni/download/jni.pdf

1 голос
/ 30 сентября 2010

Пока у вас есть ответ, я не думаю, что было предоставлено слишком много информации о возможной основной причине. Вот несколько возможностей, но есть и другие. Обратите внимание, что это относится к Windows.

Включен COM-объект с резьбой в квартире. Квартирные COM-объекты с резьбой, которые являются единственными типами, которые может создавать VB, могут использоваться только в потоке, который их создает.

Функции безопасности, такие как олицетворение, часто изолированы от потоков. Если код инициализации изменил контекст потока, будущие вызовы, которые ожидают, что контекст будет на месте, завершатся неудачей.

Хранение памяти в отдельных потоках - это методика, используемая в некоторых приложениях для поддержки многопоточности (в Java также есть такая возможность).

...