Agentes en AjSharp (Parte 1)

Comencé a implementar ideas de agentes en mi intérprete AjSharp. Ya había explorado channel2, queue channels y futures en anteriores posts:

Channels and GoRoutines in AjSharp (Part 1)
Channels and GoRoutines in AjSharp (Part 2)
GoRoutines y Canales en C#
GoRoutines and Channels in C#
AjSharp: Implementing Futures
AjSharp: Implementando Futures
Queue Channels in AjSharp
Queue Channels en AjSharp

y una implementación paralela de un algoritmo directamente en C#:

Genetic Algorithms using GoRoutines and Channels in C#
Algoritmos Genéticos usando GoRoutines y Channels en C#

pero sería interesante tener un modelo más claro para la computación en paralelo. En programas no triviales, el manejo de varios canales puede ser “convoluted”, como mostré en la implementación del algoritmo genético. Así que extendí el lenguaje para soporte objetos similares a agentes, en dirección a un actor model parcial, con declaraciones como:

agent MyAgent {
    // ....
}


Una definición de agente es como la definición de una clase. Podemos crear agentes usando el operador new y constructores:



agent = new MyAgent(parameter);


La diferencia con un objeto es: cuando una instancia de agente es creada, se lanza un background thread para procesar una cola, donde se van guardando las llamadas a métodos del agente y sus parámetros. Esas llamadas son procesadas por el thread el propio agente. Un ejemplo:



agent IncrementAgent 
{
  sub Process(channel, value)
  {
    channel <- value + 1;
  }
}
myagent = new IncrementAgent();
channel = new Channel();
myagent.Process(channel, 1);
result = <-channel; // result == 2


La llamada myagent.Process(..) no se ejecuta en el thread principal, sino que es guardada en el queue interno del agente, y será procesada por el thread propio del agente. En la actual implementación, la queue interna es un queue channel, así que la sincronización entre invocadores y el agente es automática.



Para implementar este tipo de agentes, agregué la clase AgentFunction, representado el método a llamar. Cuando se lo invoca, no se ejecuta directamente sino que su invocación es enviada a la cola del agente:



public object Invoke(IBindingEnvironment environment, object[] arguments)
{
    AgentObject agent = (AgentObject)((ObjectEnvironment)environment).Object;
    agent.SendInvoke(this.function, environment, arguments);
    // TODO if function, return a Future
    return null;
    // Old direct code
    //            return this.function.Invoke(environment, arguments);
}


La llamada SendInvoke pone los datos de la invocación en la cola de proceso del agente.



Hay una AgentClass, derivada de DynamicClass (que es la definición normal de una clase dinámica en AjSharp), extendida con una nueva manera de crear una instancia:



public override object NewInstance(object[] parameters)
{
    AgentObject dynobj = new AgentObject(this);
    this.NewInstance(dynobj, parameters);
    dynobj.Launch();
    return dynobj;
}


La clave está en dynobj.Launch() que lanza el thread de proceso que ejecutará todas las llamadas al agente, tomando sus datos de la cola interna.



De esta forma, llamar a un método de un agente recuerda a una message passing invocation. Si recordamos Smalltalk, una llamada es en realidad un mensaje con nombre y argumentos. En mi opinión, al usar la convención de <agente>.<método>(<parametros>) es una forma clara y transparente de implementar un message passing usando sintaxis “normal”.



Esta es la actual implementación ingenua del agente:



    public class AgentObject : DynamicClassicObject
    {
        // TODO 100 is hard coded
        private QueueChannel channel = new QueueChannel(100);
        public AgentObject(IClass objclass) 
            : base(objclass)
        {
        }
        public IChannel Channel { get { return this.channel; } }
        public void Launch()
        {
            Thread thread = new Thread(new ParameterizedThreadStart(this.Execute));
            thread.IsBackground = true;
            thread.Start(Machine.Current);
        }
        public void SendInvoke(ICallable function, IBindingEnvironment environment, object[] arguments)
        {
            AgentTask task = new AgentTask() { Callable = function, Environment = environment, Arguments = arguments };
            this.channel.Send(task);
        }
        private void Execute(object parameter)
        {
            Machine machine = (Machine) parameter;
            machine.SetCurrent();
            while (true)
            {
                object obj = this.channel.Receive();
                AgentTask task = (AgentTask) obj;
                task.Callable.Invoke(task.Environment, task.Arguments);
            }
        }
    }
    internal class AgentTask
    {
        internal ICallable Callable;
        internal IBindingEnvironment Environment;
        internal object[] Arguments;
    }


Como siempre, el código está publicado en:



http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjLanguage



Próximos pasos:



  • Explorar el proceso de agente con un ejemplo. Podría portar el ejemplo de algoritmo genético que escribí en C# o anteriores ejemplos míos de web crawler.
  • Soportar la invocación de funciones que devuelvan valores en un agente: el resultado de retorono sería un future, que sería llenado por el agente en paralelo. Problemas: estaríamos en algún momento esperado el valor del future, con posibles dead-locks.
  • Algo más ambicioso: agentes distribuidos. Que el new o algo similar, provoque que el agente se ejecute en otra máquina, y nosotros lo manipulamos como siempre a través de un proxy.


Nos leemos!



Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

This entry was posted in 12677, 8870, 8926. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>