Howto многопоточные скрипты jython, запущенные из java?
Я создаю фреймворк в Java, который будет прослушивать события, а затем обрабатывать их в Jython. Различные типы событий будут отправляться по разным сценариям.
Так как jython занимает довольно много времени, чтобы скомпилировать script, когда вызывается PythonInterpreter.exec(), мне придется предварительно скомпилировать скрипты. Я делаю это следующим образом:
// initialize the script as string (would load it from file in final version)
String script = "print 'foo'";
// get the compiled code object
PyCode compiled = org.python.core.__builtin__.compile( script, "<>", "exec" );
Скомпилированный объект PyCode будет помещен в репозиторий и использован как события, входящие в
PythonInterpreter pi = new PythonInterpreter();
pi.set( "variable_1", "value_1");
pi.set( "variable_x", "value_x");
pi.exec( compiled );
Теперь для моей загадки - может случиться, что одновременно происходит несколько событий определенного типа - при этом одновременно выполняются несколько экземпляров script.
Почти все скрипты, вероятно, останутся недолговечными - до 100 строк, без петель. Число и частота полностью случайны (генерируемые пользователем события) и могут составлять от 0 до 200 в секунду для каждого типа события.
Каким будет лучший способ сделать это? Я рассматриваю несколько возможностей:
- использовать синхронизацию в точке события триггера - это предотвратит несколько экземпляров одного и того же script, а также события не будут обрабатываться так быстро, как они должны быть
- создать пул скриптов того же типа, который каким-то образом заполняется клонированием исходного объекта PyCode - самая большая проблема, вероятно, будет оптимизировать размеры пула
- динамически клонирует объект script от родителя при необходимости, а затем отбрасывает его, когда exec() заканчивается - таким образом, отставание удаляется из компиляции, но оно все еще присутствует в методе clone
Возможно, комбинация чисел 2 и 3 была бы лучшая - создание динамических размеров пула?
Итак, какие-то мысли?;)
Ответы
Ответ 1
Жаль, что экземпляры PyCode
не являются неизменяемыми (в классах много публичных членов).
Вы можете предварительно скомпилировать повторно используемый script с помощью этого кода:
// TODO: generate this name
final String name = "X";
byte[] scriptBytes = PyString.to_bytes(script);
CompilerFlags flags = Py.getCompilerFlags();
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
Module.compile(parser.parse(new ByteArrayInputStream(scriptBytes), "exec",
"<>", flags), ostream, name, "<>", false, false, false, flags);
byte[] buffer = ostream.toByteArray();
Class<PyRunnable> clazz = BytecodeLoader.makeClass(name, null, buffer);
final Constructor<PyRunnable> constructor = clazz
.getConstructor(new Class[] { String.class });
Затем вы можете использовать конструктор для создания экземпляров PyCode для script, когда вам это нужно:
PyRunnable r = constructor.newInstance(name);
PyCode pc = r.getMain();
Я был бы первым, кто признал бы, что это не очень хороший способ сделать что-то и, вероятно, говорит о моей неопытности с Jython. Однако он значительно быстрее, чем компиляция каждый раз. Код работает под Jython 2.2.1, но не будет компилироваться под Jython 2.5 (и не будет вашим).
Ответ 2
PythonInterpreter стоит дорого, этот код будет использовать только один.
#action.py
def execute(filename, action_locals):
#add caching of compiled scripts here
exec(compile(open(filename).read(), filename, 'exec'), action_locals)
//class variable, only one interpreter
PythonInterpreter pi;
//run once in init() or constructor
pi = new PythonInterpreter();//could do more initialization here
pi.exec("import action");
//every script execution
PyObject pyActionRunner = pi.eval("action.execute");
PyString pyActionName = new PyString(script_path);
PyDictionary pyActionLocals = new PyDictionary();
pyActionLocals.put("variable_1", "value_1");
pyActionLocals.put("variable_x", "value_x")
pyActionRunner.__call__(pyActionName, pyActionLocals);
#example_script.py
print variable_1, variable_x