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].
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:
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.
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++; }The main program is given in Agent1.java. As in our previous example, the
done()
method terminates the behavioiur 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: aaaThe 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
And this is the output: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; } }
jean% java jade.Boot john:Bad1 --- Message 1 --- - message 2 10: john 310: john 620: john 920: john 1220: john 1530: johnThis isn't what we had expected. There are no delays: the two messages are printed instantly within the first 10 msec. The explanation is that:
block(dt) doesn't block; it just delays 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 again.
Here are the results: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; } }
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.
and the output: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; } }
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.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
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
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. More important, the program ends and we get back to the shell prompt without having to do a CTL-C.
- Top
- Previous
- Next