2009-08-11 9 views
10

Je suis en train de construire un framework en Java qui va écouter les évènements et les traiter ensuite en Jython. Différents types d'événements seront envoyés à différents scripts.Comment faire pour exécuter des scripts jython multithread exécutés à partir de Java?

Étant donné que jython prend un certain temps à compiler le script lorsque PythonInterpreter.exec() est appelé, je vais devoir pré-compiler les scripts. Je fais de la façon suivante:

// 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"); 

L'objet compilé PyCode serait poussé au référentiel et utilisé comme des événements viennent dans

PythonInterpreter pi = new PythonInterpreter(); 
pi.set("variable_1", "value_1"); 
pi.set("variable_x", "value_x"); 
pi.exec(compiled); 

Maintenant pour mon énigme - il peut arriver qu'il y ait plusieurs des événements de certains types se produisent en même temps - donc plusieurs instances de script s'exécutant en même temps.

Presque tous les scripts resteraient probablement de courte durée - jusqu'à 100 lignes, sans boucles. Le nombre et la fréquence sont complètement aléatoires (événements générés par l'utilisateur) et peuvent aller de 0 à environ 200 par seconde par type d'événement.

Quelle serait la meilleure façon de le faire? Je regarde quelques possibilités:

  1. synchronisation d'utilisation au point d'événement déclencheur - cela empêcherait plusieurs instances de même script, mais aussi des événements ne serait pas traitée aussi rapidement qu'ils devraient être
  2. créer une piscine des scripts de même type en quelque sorte peuplés par le clonage de l'objet PyCode original - le plus gros problème serait probablement l'optimisation des tailles de pool
  3. clonez dynamiquement l'objet de script du parent chaque fois que nécessaire, puis supprimez-le quand exec() se termine - de cette façon le décalage est supprimé à partir de la compilation mais il est toujours présent dans la méthode de clonage

Probablement la combinaison des numéros 2 et 3 serait la meilleure - création de tailles de pools dynamiques?

Alors, des pensées? ;)

Répondre

3

Il est dommage que PyCode instances ne sont pas immuables (il y a beaucoup de membres du public sur les classes).

Vous pouvez précompiler un script réutilisable en utilisant ce code:

// 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 }); 

Vous pouvez alors utiliser le constructeur pour produire des instances de PyCode pour le script chaque fois que vous en avez besoin:

PyRunnable r = constructor.newInstance(name); 
PyCode pc = r.getMain(); 

je serais le D'abord admettre que ce n'est pas une bonne façon de faire les choses et en dit probablement long sur mon inexpérience avec Jython. Cependant, il est significativement plus rapide que la compilation à chaque fois. Le code fonctionne sous Jython 2.2.1, mais ne compilera pas sous Jython 2.5 (ni le vôtre).

+0

Nice! Fonctionne comme un charme;) Il est à noter qu'une instance de PythonInterpreter DOIT être créée avant le premier appel de Module.compile (...). Si NullPointerException n'est pas lancé à partir de SyspathJavaLoader.loadClass() Vous avez été des plus utiles. Maintenant tout ce que j'ai à faire est de l'intégrer dans un pool de scripts dynamiquement redimensionnable ... – nEJC

+0

Connaissez-vous un équivalent pour Jython 2.5? – Laurent

+0

@Laurent - non, je n'ai pas regardé cette zone depuis que j'ai posté cette réponse – McDowell

1

PythonInterpreter est cher, ce code n'en utilisera qu'un seul.

#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 
+0

Intéressant, mais pour autant que je sache PythonInterpreter n'est pas thread-safe, donc ce n'est probablement pas une bonne idée (au moins pour moi) .. devra faire quelques tests si ... – nEJC

+0

Oui, PythonInterpreter n'est pas thread-safe, c'est exactement la raison pour laquelle je l'ai fait de cette façon. Pi.eval ("action.execute") ne vous donne qu'une instance de la méthode en tant qu'objet java, ne l'exécute pas. – Ron