Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[docs] Add tutorials on parallel execution and mas initialization.
close #817 Signed-off-by: Stéphane Galland <galland@arakhne.org>
- Loading branch information
1 parent
cd02ed3
commit a5cc3da
Showing
4 changed files
with
331 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
docs/io.sarl.docs.markdown/src/main/documentation/tutorials/MASInitialization.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# Initialization of a Multiagent System | ||
|
||
[:Outline:] | ||
|
||
This document describes the key elements and the best practices for initializing and starting up a multiagent system. | ||
Before reading this document, it is recommended reading | ||
the [General Syntax Reference](../reference/GeneralSyntax.md). | ||
|
||
Initialization of a multiagent system is a hard problem, and specifically with SARL in which the | ||
[agent spawning is run in parallel](./ParallelExecution.md). | ||
|
||
The problem is: how ensuring that all the agents on the system are alive before starting to run their | ||
standard behaviors? | ||
|
||
## Example of a problematic system | ||
|
||
First, let consider a SARL code that is not working well regarding the initialization of the system. | ||
In the following code, the type of agent [:myagentname:] is defined. | ||
This type of agent emits an [:myeventname:] event when the application starts, i.e. when the agent is initialized. | ||
It also logs the message [:msg1:] when the agent is initialized. | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.masinitialization | ||
import io.sarl.core.DefaultContextInteractions | ||
import io.sarl.core.Logging | ||
import io.sarl.core.Initialize | ||
[:On] | ||
event [:myeventname](MyEvent) | ||
|
||
agent [:myagentname](MyAgent) { | ||
uses DefaultContextInteractions, Logging | ||
[:initializeblock](on Initialize) { | ||
[:emit](emit)(new MyEvent) | ||
} | ||
|
||
[:myeventblock](on MyEvent) { | ||
info([:msg1]("Event received")) | ||
} | ||
} | ||
[:End:] | ||
|
||
For starting the system, we could define a booting agent that starts 100 agents of the previously defined type: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.masinitialization | ||
import io.sarl.core.Initialize | ||
import io.sarl.core.Lifecycle | ||
event MyEvent | ||
agent MyAgent { | ||
} | ||
[:On]agent BootAgent { | ||
uses Lifecycle | ||
on Initialize { | ||
for (i : 1..100) { | ||
[:spawnfct](spawn)(typeof(MyAgent)) | ||
} | ||
killMe | ||
} | ||
} | ||
[:End:] | ||
|
||
There is no warranty that the following sentences are true: | ||
* The order of the agent initialization is the same as the order of the [:spawnfct:] calls; | ||
* When the spawning loop is finished, all agents are spawned. | ||
* The number of logged messages is always equal to 100 * 100 = 10,000, i.o.w. 100 spawned agents that are receiving the events from itself and the other agents. | ||
|
||
But, according to the operational semantic of the spawning loop, the number of received messages | ||
is defined by the mathematical suite f(n) = f(n-1) + n = (n(n+1))/2, illustrated in the table below. | ||
For 100 agents, the number of logged messages should be 5,050, not 10,000. | ||
|
||
|
||
| Number of spawned agents | 1 | 2 | 3 | 4 | 5 | | ||
|-----------------------------|---|---|---|----|----| | ||
| Number of received messages | 1 | 3 | 6 | 10 | 15 | | ||
|
||
|
||
Moreover, According to the expected [agent spawning's parallel execution](./ParallelExecution.md), the calls to the | ||
[:spawnfct:] function form a sequence of 100 calls; And, each call to the [:spawnfct:] function starts a spawning task that is run within a separated thread. | ||
Consequently, the general behavior of the system is not deterministic. | ||
We cannot infer the number of messages that will be logged because some event may be fired by agents when several | ||
other agents are still waiting for their spawns. The only one fact is that the number of logged messages is lower than or equal to f(100). | ||
|
||
## Solution: waiting for the agent spawning | ||
|
||
A possible solution to the previously mentionned problem is to split the starting up of the application into two steps: | ||
1. Spawning of the agents: agents are spawn, and the system waits until all the agents are alive and ready. | ||
2. Application start: a specific event is given to all the agents for notifying them that they could start their "standard" behaviors. | ||
|
||
Consequently, the agent's code may be redefined as follow: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.masinitialization | ||
import io.sarl.core.DefaultContextInteractions | ||
import io.sarl.core.Logging | ||
event MyEvent | ||
[:On] | ||
event [:startappevent](StartApplication) | ||
|
||
agent MyAgent { | ||
uses DefaultContextInteractions, Logging | ||
[:initializeblock](on StartApplication) { | ||
[:emit](emit)(new MyEvent) | ||
} | ||
|
||
[:myeventblock](on MyEvent) { | ||
info([:msg2]("Event received")) | ||
} | ||
} | ||
[:End:] | ||
The agent emits the [:myeventname:] event only when the application has started. | ||
This application-start event is represented by the [:startappevent:] event. | ||
|
||
The booting agent becomes: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.masinitialization | ||
import io.sarl.core.Initialize | ||
import io.sarl.core.Lifecycle | ||
import io.sarl.core.AgentSpawned | ||
import io.sarl.core.DefaultContextInteractions | ||
event StartApplication | ||
agent MyAgent { | ||
} | ||
[:On]agent BootAgent { | ||
uses Lifecycle, DefaultContextInteractions | ||
var count = 0 | ||
on Initialize { | ||
for (i : 1..100) { | ||
spawn(typeof(MyAgent)) | ||
} | ||
} | ||
on [:agentspawnedevent](AgentSpawned) [!it.agentIdentifiers.contains(ID)] { | ||
count++ | ||
if (count == 100) { | ||
emit(new StartApplication) [it.UUID != ID] | ||
killMe | ||
} | ||
} | ||
} | ||
[:End:] | ||
|
||
The two major steps of the multiagent system initialization are implemented. | ||
First, when the boot agent starts its life, it is spawning all the agents. | ||
Each time an agent is spawned, the booting agent is notified with an [:agentspawnedevent:] event. | ||
When the number of spawned agents reaches 100, the booting agent notifies about the application start | ||
and commits a suicide. | ||
|
||
[:Include:](../legal.inc) |
169 changes: 169 additions & 0 deletions
169
docs/io.sarl.docs.markdown/src/main/documentation/tutorials/ParallelExecution.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Parallel execution within the agents | ||
|
||
[:Outline:] | ||
|
||
This document describes the key features of SARL that are run in parallel on the SARL Runtime Environment (SRE). | ||
Before reading this document, it is recommended reading | ||
the [General Syntax Reference](../reference/GeneralSyntax.md). | ||
|
||
Each SRE provides a support for running the agents. Depending on the specifications of the SRE, the | ||
parallel execution of the agent's components may be used at different places. | ||
Nevertheless, according the SARL language's specifications, several features are assumed to be run in parallel. | ||
They are briefly explained below. | ||
|
||
## Event Firing, Dispatching and Handling | ||
|
||
In SARL, event-based communication is the interaction mechanism that is provided by default. | ||
Firing an event is done within an interaction space by calling one of the dedicated function that are defined within | ||
the [`[:defaultcontextinteractions](DefaultContextInteractions)`](../reference/bic/DefaultContextInteractions.md), | ||
[`[:defaultcontextinteractions](ExternalContextAccess)`](../reference/bic/ExternalContextAccess.md) and | ||
[`[:defaultcontextinteractions](InnerContextAccess)`](../reference/bic/InnerContextAccess.md) capacities. | ||
|
||
The event firing mechanism could be divided into three steps: | ||
1. Event firing: the event is fired by a source object; | ||
2. Event dispatching: the event is routed to the agents that should receive the event; and | ||
3. Event handling: the event is provided to each agent, and the defined event handlers (named behavior units) are run. | ||
|
||
Each of these steps are basically run in different threads. | ||
Let the following code: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.parallelexecution | ||
import io.sarl.core.DefaultContextInteractions | ||
import io.sarl.core.Logging | ||
import io.sarl.core.Initialize | ||
agent PongAgent { } | ||
agent PingAgent { } | ||
event MyEvent | ||
[:On]agent MyAgent { | ||
uses DefaultContextInteractions, Logging | ||
|
||
[:initializeblock](on Initialize) { | ||
[:emit](emit)(new MyEvent) | ||
info([:msg1]("Event sent")) | ||
} | ||
|
||
[:myeventblock](on MyEvent) { | ||
info([:msg2]("Event received")) | ||
} | ||
} | ||
[:End:] | ||
|
||
The call to [:emit:] is run within the thread of the calling block, i.e. [:initializeblock:]. | ||
The event is provides to the SRE, that is routing this event within a dedicated "hidden" thread. | ||
Consequently, the call to [:emit:] returns quickly. And, there is no warranty that the event's routing | ||
is started nor terminated when the function returns. | ||
|
||
In order to allow the parallel treatment of the events by an agent, each event handler, e.g. [:myeventblock:] | ||
is run in a dedicated thread. | ||
|
||
<caution>In the previous example, there is no warranty about the order of printing of the two messages. Because of the parallel execution | ||
of the threads, the [:msg2:] message may be displayed before the [:msg1:] message.</caution> | ||
|
||
## Agent Spawning | ||
|
||
Agent spawning is the action to create and start an agent from another agent. | ||
The spawning function is provided by the | ||
[`[:Lifecycle](Lifecycle)`](../reference/bic/Lifecycle.md) capacity. | ||
|
||
The agent spawning process is divided into several steps: | ||
1. Call of the spawning function; | ||
2. Creation of the agent within the computer memory; | ||
3. SRE-specific initialization of the agent capacities and internal fields; | ||
4. Synchronous execution of the [:initializeblock] of the agent; | ||
5. Firing of the [:agentspawnedevent](AgentSpawned) event. | ||
|
||
Step 1 is run within the thread of the caller. | ||
Steps 2 to 5 are run within an internal thread of the SRE. | ||
|
||
Let the following code: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.parallelexecution | ||
import io.sarl.core.Lifecycle | ||
import io.sarl.core.Logging | ||
import io.sarl.core.AgentSpawned | ||
import io.sarl.core.Initialize | ||
agent PongAgent { } | ||
agent PingAgent { } | ||
event MyEvent | ||
[:On]agent MyAgent { | ||
uses Lifecycle, Logging | ||
|
||
[:initializeblock](on Initialize) { | ||
spawn(typeof(MyAgent2)) | ||
info([:msg1]("Spawn query called")) | ||
} | ||
|
||
[:agentspawnedblock](on AgentSpawned) { | ||
info([:msg2]("Agent was spawned")) | ||
} | ||
} | ||
agent MyAgent2 { | ||
uses Logging | ||
|
||
[:initializeblock!] { | ||
info([:msg3]("Do initialization")) | ||
} | ||
} | ||
[:End:] | ||
|
||
The [:msg3:] message is always logged before the [:msg2:] message because the executed code corresponds to | ||
steps 5 and 4, respectively. These steps are run on the same thread. | ||
|
||
But, there is no warranty about when the [:msg3:] message is logged. According to the parallel execution, | ||
it may be logged at any time. Consequently, the possible output cases are: | ||
* [:msg1:], [:msg3:], [:msg2:] | ||
* [:msg3:], [:msg1:], [:msg2:] | ||
* [:msg3:], [:msg2:], [:msg1:] | ||
|
||
|
||
## Agent Killing | ||
|
||
Agent killing is the action to stop and destroy an agent. | ||
The killing function is provided by the | ||
[`[:Lifecycle](Lifecycle)`](../reference/bic/Lifecycle.md) capacity. | ||
|
||
The agent killing process is divided into several steps: | ||
1. Call of the killing function; | ||
2. Synchronous execution of the [:destroyblock] of the agent; | ||
3. SRE-specific destruction of the agent; | ||
4. Firing of the [:agentdestroyevent](AgentKilled) event. | ||
|
||
Step 1 is run within the thread of the caller. | ||
Steps 2 to 4 are run within an internal thread of the SRE. | ||
|
||
Let the following code: | ||
|
||
[:Success:] | ||
package io.sarl.docs.tutorials.parallelexecution | ||
import io.sarl.core.Lifecycle | ||
import io.sarl.core.Logging | ||
import io.sarl.core.AgentKilled | ||
import io.sarl.core.Initialize | ||
import io.sarl.core.Destroy | ||
agent PongAgent { } | ||
agent PingAgent { } | ||
event MyEvent | ||
[:On]agent MyAgent { | ||
uses Lifecycle, Logging | ||
|
||
[:initializeblock](on Initialize) { | ||
killMe | ||
} | ||
|
||
[:destroyblock](on Destroy) { | ||
info([:msg1a]("Do destruction")) | ||
} | ||
|
||
[:agentspawnedblock](on AgentKilled) { | ||
info([:msg2b]("Agent was killed")) | ||
} | ||
} | ||
[:End:] | ||
|
||
The [:msg2b:] message is always logged after the [:msg1a:] message because the executed code corresponds to | ||
steps 4 and 3, respectively. These steps are run on the same thread. | ||
|
||
|
||
[:Include:](../legal.inc) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters