Jade Primer

Parallelism and behaviours

    Introduction
  1. Installing the software
  2. Your first programs
  3. --> Parallelism and Behaviours
  4. Agent communication
  5. Using the Directory Facilities (DF)
  6. Using Jade Behaviours
  7. Using ontologies
  8. Graphical Interfaces
  9. Mobility
It is in the nature of agents to operate independently and to execute in parallel with other agents. The obvious way to implement this is to assign a java Thread to each agent - and this is what is done in Jade.

However, there is often the need for further parallelism within each agent because an agent may be involved in negotiations with other agents and each negotiation should proceed at its own pace. We could use additional Threads to handle each concurrent agent activity but this becomes very inefficient because Java Threads (in spite of the light-weight connotation of the name) were not designed for large-scale parallelism. Rather, they were designed to allow Java programs to exploit the real parallelism of multi-processor architectures and, in current Java releases, each Java Threads requires one OS Thread. This means that passing control from one Thread to another, is about 100 times slower than simply calling a method.

In order to support efficiently parallel activities within an agent, Jade has introduced a concept called Behaviour [US readers take note: Behaviour, not Behavior].

Behaviours

A behaviour is basically an Event Handler, a method which describes how an agent reacts to an event. Formally, an event is a relevant change of state; in practical terms, this means: reception of a message or a Timer interrupt. In Jade, Behaviours are classes and the Event Handler code is placed in a method called action.

Although the use of Behaviours promotes efficiency, it doesn't simplify programming. Consider coding the steps in a negotiation: sending offers, waiting for counter-offers and finally reaching agreement. This activity consists of an alternation of active phases - when the agent decides what to do and sends messages - and passive phases - when the agent waits for an answer. Threads can pause in the middle of execution to wait for messages and continue without losing context. So if we use Threads, the sequence of activities maps directly into sequences of instructions. Not so when we use Behaviours.

Behaviour actions are methods, executed one after the other by the agent's Thread after events. Like listeners in graphic interfaces, they cannot pause without blocking all other activity [within the agent]. So this is the important thing to remember about Behaviours is that:

Each Behaviour execution corresponds to ONE SINGLE instantaneous active phase.

To implement long-term activities like a negotiation, we have to provide as many different Behaviours as there are active phases in the activity. We must also arrange for them to be created and triggered in the right sequence. Actually, in Jade, as our examples will show, this isn't very hard to do.

Agent1: parallel behaviours

In our first example, Agent1, the agent just creates two instances of a Looper behaviour.
    public class Agent1 extends Agent 
    {
        protected void setup() 
        {
            addBehaviour( new Looper( this, 300 ) );
            addBehaviour( new Looper( this, 500 ) );
        }
    }
The parameters (300 and 500) mean that the first Looper should print a line every 300 ms and the second every 500 ms. The Looper behaviour is described in a seperate file. This behaviour prints out a message with the elapsed time and the agent's name every dt milliseconds, where dt is the parameter. As shown below, the action method uses a new primitive block( <delay in msec> ) which takes the behaviour out of the active queue and starts a timer to make it active again after the prescribed delay [Note: the behaviour would also be reactivated if a message were received or the agent restarted].
    public void action() 
    {
        System.out.println( tab + 
           (System.currentTimeMillis()-t0)/10*10 + ": " +
            myAgent.getLocalName() );
        block( dt );
        n++;
    }
As in our previous example, the done() method in Looper.java terminates after 6 executions. The rest of the code deals with formatting the trace with a timestamp.

Below we show the output from running Agent1. To clarify the trace, we've removed Jade's standard messages: version and Main-Container address:

    jean% java jade.Boot aaa:Agent1

        0: aaa
            0: aaa
        300: aaa
            510: aaa
        620: aaa
        920: aaa
            1010: aaa
        1220: aaa
            1520: aaa
        1530: aaa
            2020: aaa
            2530: aaa
The output clearly show the interleaving of the active phases of the two behaviours. The timestamps also show that, due to overhead, delays are not always exact.

The parallel behaviour is even more apparent if we start two Agent1 agents, 'aa' and 'zzzzz'.

    jean% java jade.Boot aa:Agent1 zzzzz:Agent1
    
        0: zzzzz
                0: aa
            10: zzzzz
                    10: aa
        300: zzzzz
                310: aa
            510: zzzzz
                    510: aa
        610: zzzzz
                610: aa
        910: zzzzz
                920: aa
            1020: zzzzz
                    1020: aa
        1220: zzzzz
                1220: aa
        1520: zzzzz
                1520: aa
            1520: zzzzz
                    1530: aa
            2030: zzzzz
                    2030: aa
            2530: zzzzz
                    2530: aa

Delaying sequential actions

For the rest of this parallelism tutorial, we will attempt to solve a more complex sequencing problem in a variety of ways. More exactly, we want to combine a sequence of two actions with the Looping behaviour that we showed previously. In the sequential part, we want our agent to
  1. wait 0.25 sec then print out a first message
  2. wait 0.5 sec and print out a second message
  3. then terminate
Meanwhile, the Looper should print out trace messages every 0.3 sec - as long as the agent is alive. In order to illustrate intricacies of behaviour scheduling, we will present several erroneous versions before getting to the correct code. Our first attempt, Bad1.java, is shown below:
public class Bad1 extends Agent 
{
    
    protected void setup() 
    {
        addBehaviour( new TwoStep() );
        addBehaviour( new Looper( this, 300 ) );
    }
}


class TwoStep extends SimpleBehaviour
{   
    public void action() 
    {
        block(250);
        System.out.println( "--- Message 1 --- " );
        block(500);
        System.out.println( "    - message 2 " );
        finished = true;
    }
    
    private boolean finished = false;
    public  boolean done() {  return finished;  }
}
And this is the output:
    jean% java jade.Boot john:Bad1
    
    --- Message 1 --- 
        - message 2 
    
        10: john
        310: john
        620: john
        920: john
        1220: john
        1530: john
Jade beginners often think that block is the equivalent of sleep. Therefore they are surprised that the two messages are printed without delay within the first 10 msec. The explanation is that:

block(dt) doesn't block; it just sets a delay for the next execution of the behaviour

In our example, the action method was executed completely right after setup() and the two messages were printed. The calls to block meant that the next execution of the behaviour was scheduled for some time in the future; but because finished was set to true, the behaviour never got to execute a second time.

Block2: effect of 2 sequential blocks

Block2.java is a program designed to see what happens to delays if we call block() twice within a behaviour action. The agent sets up a single behaviour, BlockTwice, with 2 block statements; no Looper is used. The behaviour prints out the clock at the beginning of the action and after each block(..) statement. The actions are executed 3 times. Here is the Behaviour:
class BlockTwice extends SimpleBehaviour
{   
    static long t0 = System.currentTimeMillis();
    
    public void action() 
    {
        System.out.println( "Start: " 
                   + (System.currentTimeMillis()-t0) );
        block(250);
        System.out.println( "   after block(250): " 
                   + (System.currentTimeMillis()-t0) );
        block(1000);
        System.out.println( "   after block(1000): " 
                   + (System.currentTimeMillis()-t0) );
        System.out.println();
    }
    
    private int n = 0;
    public  boolean done() {  return ++n > 3;  }
}

Here are the results:
    jean% java jade.Boot tom:Block2

    Start: 1
       after block(250): 7
       after block(1000): 9
    
    Start: 258
       after block(250): 261
       after block(1000): 263
    
    Start: 512
       after block(250): 515
       after block(1000): 517
    
    Start: 767
       after block(250): 769
       after block(1000): 771
    

As we previously noted, the calls to block(..) don't introduce any delay between the statements in the action method. Furthermore, successive behaviour executions occur 250 msec apart and we conclude that only the first invocation of block is significant. Further attemps to block a blocked behaviour have no effect.

Bad3: problems using sleep()

We now return to the problem of combining our sequential actions with a looping behaviour. To make sure that there is a delay between active phases, it is tempting to use Thread.sleep(dt) instead of using block(dt). However, remember that each agent only has ONE Thread which is shared by the behaviours. If that Thread is put to sleep anywhere, then all activity for that agent stops. Here is the outline of the behaviour code ( full version is in Bad3.java ):
    class TwoStep extends SimpleBehaviour
    {   
        public void action() 
        {
           try
           {  System.out.println( "--- TwoStep start: " + ...time );
              Thread.sleep(200);
              System.out.println( " -- Message 1 ---: " + ...time );
              Thread.sleep(500);
              System.out.println( "  - message 2    : " + ...time );
           }
           catch (Exception e) {}
        }
        
        private int n = 0;
        public  boolean done() {  return ++n > 2;  }
    }
and the output:
    jean% java jade.Boot mary:Bad3
    
    --- TwoStep start: 20
     -- Message 1 ---: 220
      - message 2    : 720
        700: mary
    --- TwoStep start: 730
     -- Message 1 ---: 930
      - message 2    : 1430
    --- TwoStep start: 1440
     -- Message 1 ---: 1640
      - message 2    : 2140
        2120: mary
        2430: mary
        2730: mary
        3040: mary
        3340: mary
Now the sequential actions are timed properly: message1 is printed 200 msec after the start of the behaviour and the second message 0.5 sec after that; BUT there has been no interleaving of the cyclic Looper behaviour with the TwoStep behaviour. The cyclic Looper should be activated every 300 msec, printing at times: 0, 300, 600, 900 etc.... Instead its first execution is at 700 and the second 1400 msec after that.

Agent2: a first solution to action sequences

In spite of our failures, we now have all the elements required for a solution. As we hinted at earlier, if an activity is made up of a sequence of active phases with pauses in between, the solution is to use several Behaviours, one for every active phase. In our case, we need two behaviours to print out the two messages. Ideally, we would like to be able to schedule newly created behaviours at some time in the future, so that in the behaviour which prints out Msg1, we could schedule the behaviour which prints out Msg2 to occur 0.5 sec later. Unfortunately, the method used to delay, block(), can't be used to schedule other behaviours, merely to postpone the next execution of the current behaviour. The solution is to use a state variable to distinguish the first execution of a behaviour - when we execute block(dt) - from the next execution which occurs dt msec later. The code is given in Agent2.java.

Here is the code for Step1, the first behaviour which waits 200 msec then prints out Msg1 and creates Step2, the behaviour which will print the second message and terminate the activity of the agent.

    class Step1 extends SimpleBehaviour
    {   
        int state = 0;
        
        public void action() 
        {
            if (state==0)  block( 200 );
                
            else if (state==1) 
            {
                System.out.println( "--- Message 1 --- " );
                addBehaviour( new Step2() );
            }
            state++;
        }
        
        public  boolean done() {  return state > 1;  }
    }
The pattern is simple. The state variable counts the number of times we have executed the behaviour. On the first execution, we use block to schedule the next execution 200 msec later. The next time around, we print out Message1 and create the Step2 Behaviour whose structure will be very similar to this one. In this example, Step1 is a class local to the agent so that the agent method addBehaviour() can be used directly. If this behaviour were compiled seperately we would have to write something like "myAgent.addBehaviour()". After the second execution, we have no further use for the Behaviour and we arrange for done() to return true.

Here is the code for the second behaviour:

    class Step2 extends SimpleBehaviour
    {   
        int state = 0;
        
        public void action() 
        {
            if (state==0)  
                block( 600 );
            else {
                System.out.println( "    - message 2 " );
                doDelete();   // applies to the Agent
            }
            state++;
        }
        
        public  boolean done() {  return state>1;  }
    }
The major difference is in the use of a new (Agent) method doDelete() which removes the agent from the system and terminates all its active behaviours. The complete code is given in Agent2.java and the output is shown below. Note that the Looper behaviour is interleaved with the sequential message printing but that all activity stops after the agent has been deleted.
    jean% java jade.Boot harry:Agent2

        0: harry
    --- Message 1 --- 
        340: harry
        640: harry
        - message 2 

Agent3: Final version of our combined behaviours

In this version, we combine the two behaviours into a single finite state machine (FSM). We use a switch to select the actions corresponding to each state and, to get a slightly different output, we've increased the second delay to 0.8 sec.
    class TwoSteps extends SimpleBehaviour
    {   
        int state = 1;
        
        public void action() 
        {
            switch( state ) {
            case 1:
                block( 200 );
                break;
                
            case 2:
                System.out.println( "--- Message 1 --- " );
                block( 800 );
                break;
            
            case 3:
                System.out.println( "  -- message 2 --" );
                finished = true;
                doDelete();   // applies to the Agent
            }
            state++;
        }
        
        private boolean finished = false;
        public  boolean done() {  return finished;  }
    }
The output is shown below:
    jean% java jade.Boot alice:Agent3

        0: alice
    --- Message 1 --- 
        340: alice
        640: alice
        950: alice
      -- message 2 --
    jean% 
Since the sequential part ends after 1.0 sec, we now get an extra line from the Loop behaviour.

You will notice that the program ends normally and we get back to the shell prompt without having to do a CTL-C. To get this behaviour, we provided the agent with a takeDown() method. This is called automatically when an agent is deleted. Most often, this is where an agent removes its entries from various directories; but, in our case, we stopped the container and terminate the program with "System.exit(0)". The whole program is given in Agent3.java.


Top
Previous
Next
updated feb. 23, 2004