translated by Juan Martínez Romo, juaner@gsyc.es
13 de mayo de 2006
Este documento introduce la arquitectura de componentes autónomos (ACA). Comenzamos con nuestra motivación técnica para proponer otra arquitectura basada en componentes. A continuación veremos los detalles. Específicamente, introducimos la noción de componentes, puertos, y contratos, discutimos cómo se identifican los componentes y los puertos, cómo los componentes importan/exportan información en tiempo de ejecución, y una implementación de ACA para simulaciones, llamado J-Sim. En el documento titulado “Modelo Abstracto de Red y J-Sim,” nos centraremos en la simulación de red, y presentaremos un modelo generalizado de red de intercambio de paquetes, la plataforma de simulación entre redes(INET).
En el diseño moderno de los circuitos digitales, un sistema de hardware está ensamblado a partir de chips de circuitos integrados (IC) en una placa con un circuito impreso. Un chip con un IC es una caja negra en la que la especificación de su función y los patrones de la señal de entrada/salida se encuentran completamente especificados en su manual . Cambios en las señales de entrada accionan cierta función del IC, y cambia, después de un cierto retardo , sus salidas según la especificación del chip. El hecho de que un chip con un IC esté interconectado con otros chips/módulos/sistemas solamente a través de sus patillas (y se blinda además del resto del mundo) permite que los chips con IC sean diseñados, implementados y probados, independientemente de todo los demás. Creemos que éste es uno de los factores dominantes porqué la industria del IC es tan exitosa.
Nuestra propósito de una arquitectura de componentes autónomos es una tentativa directa imitando el diseño del IC y el modelo de fabricación en términos de cómo se especifican, se diseñan, y están montados los componentes. Uno puede discutir que el paradigma de programación orientada a objetos (OO) fuera propuesto exactamente para el mismo propósito. Sí, pero esto no es bastante para alcanzar el objetivo. Echemos una ojeada cómo la función que calcula un x es implementada en el paradigma de programación OO. En el paradigma de programación procesal, el código puede parecerse a:
double exp_a(double a, double x)
{
double tmp = x * ln(a);
double ans = exp(tmp);
return ans;
}
En el paradigma de programación OO, uno puede empaquetar la función en una clase ExtendedMath (que alternadamente llame a ln () y exp () en una clase BasicMath):
class ExtendedMath
{
BasicMath bMath;
double exp_a(double a, double x)
{
double tmp = x * bMath.ln(a);
double ans = bMath.exp(tmp);
return ans;
}
.....
}
Figura 1: Relación de las dos clases, ExtendedMath y BasicMath.

Como se muestra en la imagen 1, el modelo de programación OO posee
una cierta semejanza al diseño del IC y al modelo de fabricación: un
sistema de software se compone de componentes que interactúan
el uno con el otro. Como los datos se dan a la entrada, a, x, del componente ExtendedMath, una señal a es generada y enviada al componente BasicMath. Cuando el resultado está listo, otra señal tmp es generada y enviada al componente ExtendedMath. Finalmente el resultado ax es generado en la salida poco después de que etmp.
Una pregunta interesante es entonces: si el diseño del software se
puede especificar en una manera similar al diseño del IC, ¿por qué no
puede un sistema de software alcanzar el mismo nivel de la modularidad
que en diseño del IC?
La pregunta más interesante aquí es entonces: Si el comportamiento del software se puede describir exactamente igual que el hardware, ¿por qué el software no puede alcanzar tan buena modularidad como el hardware y además no sufre el fenómeno de los subsistemas de hyperspaghetti1?
1 El fenómeno de los subsistemas de hyperspaghetti se refiere a la situación en la cual los módulos en el sistema están tan firmemente acoplados que es imposible extraer algunos de los módulos del código para su reutilización o eliminar errores de un módulo del código sin afectar a otros módulos. Ver el artículo [Bruce F. Webster, "Pitfalls of Object-Oriented Development," M&T Books, New York, 1995] para más detalles.
Creemos que la razón por la que el diseño del software no puede
alcanzar el mismo nivel de modularidad que el diseño del IC es
porque el paradigma de programación OO es fundamentalmente diferente del
diseño del hardware en la conexión de sus componentes. En el paradigma de
programación OO, una clase hace referencias directas a otras instancias de clases
y hace llamadas a las funciones expuestas por otras instancias de clase, e.g.,
ExtendedMath contiene una instancia de BasicMath. Esto implica:
exp_a () haga uso de ln () y de exp ()
solamente mirando la definición de la función. Uno debe mirar en el
código para realizar la dependencia y la interacción entre llamadores y
llamados.exp_a ()) tiene que saber los nombres exactos de los llamados (ln () y exp ()).
La información permite hacer la comprobación de tipos en tiempo de
compilación pero también introduce enlaces innecesarios a los
módulos del software. Por consiguiente, el código del software es propenso al
fenómeno de los subsistemas de hyperspaghetti, y es difícil
ser reutilizado en diversos contextos.Debido a las características antedichas, es difícil desarrollar y mantener un sistema de software OO con una colección grande de funciones y de clases. En el periodo de depuración, uno no puede obtener una vista clara de las relaciones de conexión sin adentrarse en los detalles de la implementación y las líneas de código línea por línea. Esto produce una imprevisión en el desarrollo del software y el alto coste de mantenimiento, y se llama generalmente como crisis del software.
La conexión del componente es por tanto el problema. Entonces, ¿cómo está hecho en el diseño del IC? En el diseño del IC, las señales que fluyen hacia dentro y hacia fuera del chip del IC están especificadas en la definición de interfaz. Es decir, en fase de diseño, un IC está limitado a un cierto contrato (o en la jerga del diseño del IC, la especificación del IC en el libro de especificaciones), en vez de estas limitado a los componentes que interactúan con el. La conexión del componente es distinta al momento en que un sistema(e.g., ALU) esa siendo construido.
Un contrato especifica cómo un iniciador (llamador) y un reactor (llamado) realiza cierta función. Especifica simplemente la causalidad del intercambio de información entre los componentes pero no los componentes que pueden participar en el intercambio de información. Dos componentes, actuando respectivamente como el iniciador y el reactor, están limitados en la fase de integración del sistema para satisfacer el contrato. Un sistema con todos los componentes limita a otro a que sea completo, si los iniciadores de todos los contratos implicados se satisfacen.
En el ejemplo anterior, si exp_a (), ln () y exp () se encapsulan como componente, el componente exp_a entonces está limitado a tres diversos contratos: el cálculo de ax, de ln (x) y de e x respectivamente y es un reactor de un contrato de ax y de un iniciador de los otros dos. Semejantemente, el componente ln está limitado al contrato ln (x) y el componente exp al contrato ex, ambos como reactores. El concepto se ilustra en la figura 2, abajo.
Figura 2: Tres componentes (exp_a, ln y exp)
y los contratos están limitados. Las líneas gruesas entre los
contratos indican que los contratos están emparejados el uno al
otro. Los tres componentes forman un sistema completo con los contratos de ln () y ex satisfechos, dejando el contrato de ax incumplido con un iniciador que falta. Observa que el componente ln o exp también está considerado como un sistema completo.

Nosotros reclamamos que los contratos de conexión en la fase de diseño y los componentes en la fase de construcción del sistema eliminen el fenómeno del hyperspaghetti. La información necesitada para atar los contratos se define en el interfaz de un componente. De esta manera, la interconexión de componentes está bien especificada y los programadores no tienen que indagar en los detalles de la implementación para encontrar esa información. Esto evita que el fenómeno del hyperspaghetti suceda a nivel del componente y permite la composición de componentes de una manera muy similar al diseño del IC.
Interfaz en RPC, CORBA o COM/COM+ es similar al contrato en nuestra discusión, pero el interfaz no es tan flexible como el contrato y la función real que ata en esos estándares no es tan directo como en una arquitectura basada en componentes.
Con la discusión antedicha como motivación, presentaremos a continuación la arquitectura de componentes autónomos (ACA).
En la arquitectura de componentes autónomos, una entidad básica es un componente. Cada componente posee uno o másendpoints, llamados puertos. El componente donde reside un puerto se llama el componente anfitrión del puerto. Dos componentes están conectados por sus puertos de una manera permanente. Cuando un componente envía datos a uno de sus puertos, el puerto retransmite los datos a los puertos que están conectados a él. Cuando los datos llegan a un puerto, el componente que posee el puerto procesa los datos inmediatamente en un nuevo contexto de ejecución (thread) y puede generar salidas en ciertos puertos según lo especificado en el contrato.
La salida y la entrada de un puerto están separadas por cables. Cuando los datos se envían a un puerto, se envían por el alambre de salida del puerto y llegan a los puertos por su alambre de la entrada. La figura 3 representa posibles cableados en ACA. Particularmente en (c), el alambre azul es el alambre de salida del puerto A, C y D, y el alambre de la entrada de B, de D y del E. El alambre rojo es el alambre de salida del puerto B y E, y el alambre de la entrada de C. Por ejemplo, los datos enviados por el puerto A llegarán al puerto B, D y el E. ACA no permite el lazo de envío a uno mismo. Por ejemplo, los datos enviados en el puerto D llegarán al puerto B y E, pero no a D así mismo.
Figura 3: Posible cableado entre puertos.
![]() |
![]() |
![]() |
|
| (a) Uno a uno. | (b) Uno a muchos. | (c) Muchos a muchos. |
Un puerto se puede conectar con otro puerto de manera simplex o de manera duplex. De la manera simplex, el alambre de salida del primer puerto se ata con el alambre de la entrada del segundo puerto o decimos que los dos alambres están unidos por la conexión. De la manera simplex, el alambre de la entrada del primer puerto y el alambre de la salida del segundo puerto se unen también. La figura 4 demuestra un ejemplo del cableado duplex.
Figura 4: Un ejemplo del cableado duplex. Conectando el puerto B (o E) y C en (a) resulta al unir el alambre azul marino y del alambre azul así como el alambre rojo oscuro y el alambre rojo, según las indicaciones de (b).
|
![]() |
|
(a) |
(b) |
Un contrato especifica cómo un iniciador (llamador) y un reactor (llamado) satisface cierta tarea. Particularmente, especifica la causalidad de los datos enviados/recibidos entre los componentes pero no los componentes que participan en la comunicación. Los contratos se pueden clasificar más a fondo en dos categorías: contrato del puerto y contrato del componente. Un contrato del puerto está limitado específicamente a un puerto de un componente, mientras que un contrato de un componente describe cómo un componente responde a los datos que llegan a cada uno de sus puertos (e.g., cómo el componente procesa los datos, ciertas estructuras de datos son actualizadas, y generan salidas en ciertos puertos).
ACA también soporta la noción de componente compuesto. Un componente se puede componer de varios componentes y el sistema entero forma una jerarquía de componentes. Un componente compuesto es el componente padre de los componentes encapsulados, que se llaman a su vez componentes hijos. El componente compuesto permite organizar un sistema de software con una granularidad deseable.
La figura 5 ilustra cómo el sistema de tres componentes mostrado en la Figura 2
se puede organizar en un componente compuesto. Observar que el
componente compuesto tiene un puerto A conectado con el puerto B del componente encapsulado exp_a. Cuando los datos llegan al puerto A, llegan
realmente al puerto B. Semejantemente, cuando los datos se envían al
puerto B, se envía por el puerto A al exterior del componente exp_a. Tal puerto en un componente compuesto se llama puerto en la sombra.
Lo que sucede realmente aquí es que los puertos A y B comparten el mismo alambre
de salida y el mismo alambre de entrada. Estar conectado a un puerto en la sombra,
implica estar conectado a los puertos de los componentes encapsulados dentro del componente
del puerto a la sombra que estamos conectados. Un comportamiento sería que un
componente padre no reciba datos de un puerto en la sombra y no
debe enviar datos a través de un puerto en la sombra. El
comportamiento no se define si un componente padre envía a través
de un puerto en la sombra.
Figura 5: Encapsulación del sistema de tres componentes en la Figura 2.

Un componente puede proporcionar un servicio común en uno de sus puertos a otros componentes en el sistema. Cuando un componente envía una petición a este componente para un servicio, idealmente, el componente realiza el servicio y devuelve una contestación al componente que hizo la petición. Sin embargo, la arquitectura introducida en gran medida hace que la respuesta llegue a todos los componentes que estén conectados al mismo puerto, que no es el comportamiento deseado. Para superar este problema, ACA define un tipo especial de puerto, llamado puerto del servidor.
Formalmente, un puerto del servidor está para que un componente proporcione un servicio común a otros componentes, y, específicamente, devuelva una respuesta solamente al componente que hizo la petición, sin importar que muchos componentes estén conectados con este puerto. Además, ACA especifica que el servicio esté guiado, y la respuesta se envíe, en el mismo contexto que la petición que se envió. Uno puede decir que el enviar la petición queda “bloqueado” hasta que la contestación retorna.
El puerto A del componente exp_a en la Figura 5
es un ejemplo típico de un puerto del servidor. Imagina que dos
componentes están conectados al puerto A para el servicio de
calcular ax. Un componente envía a=2, x=4 y exp_a debería contestar 16 solamente al componente de petición en lugar de a ambos componentes.
Cada componente y cada puerto en un sistema de software tienen que
ser identificados únicamente. Como la jerarquía de componentes en la
arquitectura de componentes autónomos es similar al sistema de ficheros en
un sistema operativo moderno, nosotros adoptamos un método de
nombramiento similar a ése en el sistema de ficheros de UNIX. Es
decir, un componente es identificado por el path formado recurrentemente concatenando la trayectoria del componente de padre, un separador “/”, y la identificación del componente.
Un sistema de software forma una jerarquía de componentes consigo mismo como raíz. El componente de la raíz tiene el path “/”. Cada componente y puerto en la jerarquía pueden
entonces ser identificados únicamente. Suponed que el componente padre en la Figura 5 es identificado por el path
/prefijo/exp_a
Entonces el path del componente hijo exp_a será
/prefijo/exp_a/exp_a
Los puertos en un componente se
categorizan más a fondo en diversos grupos. Cada grupo tiene una
identificación única en un componente. Cada componente tiene un grupo de puertos
por defecto con una identificación nula. La identificación
de un puerto en un componente anfitrión es el encadenamiento de la
identificación del puerto, de un separador “@” y de la
identificación de grupo. La trayectoria de un puerto se define
semejantemente como el encadenamiento de la trayectoria de su
componente anfitrión, “/” y su identificación en el componente. Por ejemplo, si el puerto A en la Figura 5 está en el grupo de puertos por defecto y el puerto B está en el grupo “a_x”, entonces el path del puerto A es
/prefijo/exp_a/A@
y el path del puerto B es
/prefijo/exp_a/exp_a/B@a_x
Nota que el separador “@” usado en la
identificación de un puerto se asegura de que un componente hijo de
un componente compuesto se puede distinguir de un puerto del mismo
componente compuesto.
Para los propósitos de diagnosis y configuración, un componente en ACA puede importar/exportar información en tiempo de ejecución a través de varios puertos señalados. La arquitectura define un puerto para tal uso, llamado puerto de información en un componente para exportar la información de diagnosis en tiempo de ejecución. También, un componente se puede equipar de uno o más puertos de eventos, que exportan un tipo específico de eventos en tiempo de ejecución. Describimos estos puertos y los formatos de la información exportada más adelante.
Cada componente se equipa de un puerto de información, llamado puerto de información. Cuatro tipos de información se pueden exportar espontáneamente en este puerto: mensaje de error, mensaje de basura, mensajes de depuración y mensajes de trazas. Toda la información exportada comparte un formato similar:
|
Los cuatro tipos de información que pueden ser exportados se enumeran abajo.
| Mensaje de error | Exportado cuando un componente no puede manejar datos entrantes (probablemente porque los datos no pueden ser reconocidos) Formato: 1. Tiempo en que ocurre el error (double) 2. Path del puerto a donde los datos llegaron (string) 3. Los datos entrantes (podría ser cualquier tipo) 4. Información de la implementación (string) (por ejemplo el lugar en donde se detecta el error) 5. Descripción detallada (string) |
| Mensaje de basura | Exportado cuando un componente desecha datos (probablemente porque se alcanza el límite de la capacidad o se viola cierta política) Formato: 1. Tiempo en que se desecha el mensaje (double) 2. Path del componente donde se desechan los datos (string) 3. Los datos desechados (podrían ser de cualquier tipo) 4. Descripción detallada (string) |
| Mensaje de depuración | Exportado cuando el escritor del componente quisiera exportar información de depuración Formato: 1. Tiempo en que se exporta el mensaje (double) 2. Sobre qué componente o puerto se realiza en mensaje (string) 3. Información detallada (string) |
| Mensaje de traza | Es un mensaje especial de depuración y se exporta para todos los datos entrantes y salientes Formato: 1. Tipo de la traza “DATA” (entrante) o “SEND” (saliente) (string) 2. Tiempo en que se registra el mensaje (double) 3. Path del puerto en donde los datos llegan o salen (string) 4. Los datos (podrían ser de cualquier tipo) 5. Información detallada (string) |
Además de la información antedicha que se puede exportar en puerto de información , un componente puede también exportar acontecimientos en los puertos señalados, llamados puertos de eventos. El formato de un mensaje de evento es:
| Mensaje del acontecimiento | Formato:
1. Tiempo en que se exporta el mensaje (double) 2. Path del puerto en el cual se exporta el mensaje (string) 3. Nombre del evento (string) 4. Objeto del acontecimiento (podría ser de cualquier tipo) 5. Información detallada (string) |
Uno puede pedir específicamente a un componente, o no, exportar ciertos tipos de información. Esto es hecho enviando un flag de 6 bits en el puerto de información de un componente. El primer bit del flag es un indicador binario que especifica permitir o inhabilitar exportando los tipos especificados de información. Los bits restantes forman una máscara que especifica qué tipo de información se solicita. El siguiente diagrama da un ejemplo de habilitación de mensajes de basura y de depuración.
| Primer bit | 1 | Acción (encendido o apagado) |
|---|---|---|
| 0 | Mensaje de error | |
| 1 | Mensaje la basura | |
| 1 | Mensaje de depuración | |
| 0 | Mensaje de traza | |
| 0 | Mensaje de exportación |
Cada componente puede exponer una colección de características. Una característica de un componente es definida por un par con un nombre y un valor. Uno puede preguntar todas las características de un componente, enviando una señal nula o una señal que contenga el nombre de la propiedad en el puerto de información del componente. El componente entonces contesta un mensaje con la propiedad que consiste en su valor.
Figura 6: Analogía entre un chip con un IC y un componente.
J-Sim es una implementación de ACA en Java. La razón de elegir Java como el lenguaje de programación es debido a muchas de sus características deseables, tales como independencia de la plataforma, la orientación a objetos pura, la sintaxis limpia del lenguaje, la incorporación de ejecución de threads, la capacidad de reflexión de Java, y la recolección automática de basura en tiempo de ejecución, que hacen la realización de ACA más fácil.
El desafío más grande en el desarrollo de J-Sim es proporcionar eficientemente contextos independientes de la ejecución o threads para que los componentes manejen datos de entrada. J-Sim introduce una hebra de gestión en background llamada runtime que es la clave del funcionamiento de J-Sim. En las siguientes secciones, veremos el concepto de runtime en general para más adelante estudiar el runtime utilizado en J-Sim.
La simulación es implementada en J-Sim como una extensión del runtime. Particularmente, el tiempo global de la simulación es observado por todos los threads activos en vez de que cada thread guarde un eje local del tiempo como el proceso lógico (LP) en paralelo y la simulación distribuida basada en eventos. Los detalles de cómo se hace la extensión se proporcionan en la sección de la Simulación de Tiempo Real Basada en Procesos.
Para proporcionar contextos independientes de la ejecución para los datos que llegan a diversos puertos de un componente en la arquitectura de componentes autónomos, es necesario un soporte especial en tiempo de ejecución. Particularmente, el runtime (en general) 2 tiene que crear un nuevo contexto de ejecución cuando los datos llegan al puerto de un componente. Representamos el proceso en la figura 7, y resumimos los pasos a continuación:
2Runtime es una colección de procesos en background (ocultos para las aplicaciones), como el recolector automático de basura en los lenguajes de programación modernos.
Figura 7: Cómo el runtime maneja los datos de llegada.

Observa que el runtime tiene control completo para crear nuevos contextos de ejecución. Para asegurarse de que el sistema de software funcione de una manera controlada, el runtime impone un límite superior al número de contextos de la ejecución que pueden ser activos simultáneamente. Cuando se alcanza el límite superior, el runtime se retrasa para crear cualquier nuevo contexto hasta que un cierto contexto existente termine su ejecución. Esto previene un número excesivo de contextos que puede agotar recursos del sistema, o en el peor caso, finalizando el sistema inesperadamente.
En J-Sim, los contextos de ejecución son implementados por
los threads de Java, con el planificador de threads en la
Máquina Virtual de Java(JVM). En la actual implementación, el tiempo de ejecución
se compone de dos clases, WorkerThread y ACARuntime:
WorkerThread envuelve al thread de Java con la información del contexto de ejecución. ACARuntime gestiona la creación y reciclado de los WorkerThreads así como la
implementación del mecanismo de control del número de WorkerThreads que pueden estar simultáneamente activos.Cuando los datos se envían a un puerto, el WorkerThread crea realmente una tarea y se la pasa a ACARuntime (como en el paso 2, arriba). Despues del acuse de recibo de la tarea, ACARuntime crea/despierta un WorkerThread para la tarea, o pone la tarea en la cola de procesos preparados hasta que un WorkerThread esté listo y la utilice.
Según lo mencionado arriba, cuando los datos se entregan a un componente, el tiempo de ejecución (ACARuntime)
crea un thread como el nuevo contexto de ejecución para
procesar los datos en el componente de recepción. Los gastos indirectos
del tiempo incurridos en en este proceso reflejan los gastos indirectos
de la comunicación entre componentes. El factor que contribuye a la
parte principal de los gastos indirectos es cómo se crean y se manejan
los threads. Un acercamiento ingenuo es crear un thread nuevo
cada vez que un nuevo contexto de ejecución es
necesario. Puesto que es generalmente costoso crear y arrancar un thread
nuevo, este acercamiento no se realiza correctamente. En vez de crear
los thredas de nuevo, ponemos en ACARuntime un pool de threads en el que los threads son reciclados
después de su ejecución y se mantienen vivos (pero dormidos).
Siempre que sean necesarios, los threads se
despiertan para servir como nuevos contextos de ejecución.
Para mejorar aún más el funcionamiento, permitimos a un thread
anunciar su preparación (en tiempo de ejecución) antes del final de
su ejecución. Es bastante común que un componente envíe un cierto
resultado al final del procesado de datos. Puesto que el thread en
el componente que envía será reciclado después de que acabe el procesado
de los datos, es natural hacer que este thread continúe
sirviendo en el componente de recepción. Para poner esto en ejecución
en tiempo de ejecución, el thread en el componente emisor
debe notificar ACARuntime por adelantado de modo que ACARuntime
no cree/despierte otro thread para procesar los datos en el
proceso emisor. Cuando el thread finaliza en el
componente emisor, después obtiene los datos de ACARuntime y sirve como nuevo contexto de ejecución en el componente de recepción. Esto, en un cierto sentido, alcanza el paradigma “un thread por mensaje según lo recomendado en la puesta en práctica de la implementación del x-kernel.
La simulación es implementada como una extensión del runtime en J-Sim. Básicamente, se asegura de que el sistema esté siempre ocupado (con WorkerThreads activos) manipulando cuidadosamente el tiempo de la simulación. Particularmente, maneja el tiempo de la simulación como sigue:
WorkerThread está activo.WorkerThreads estan activos, el tiempo de la simulación avanza al punto “futuro” más cercano en el que por lo menos un WorkerThread puede ser despertado y llegar a activarse.WorkerThread, entonces la simulación se detiene.Para alcanzar esto, guardamos tres variables:
last_time_updated:Último instante de tiempo calculado en la simulación.time_scale: cociente entre el tiempo del sistema y el tiempo de la simulación transcurridos.time_advances: cantidad de tiempo que avanza.El tiempo actual de la simulación se calcula entonces de la siguiente manera:
current_simulation_time = (current_wall_time - last_time_updated)/time_scale + time_advances;
Cuando el tiempo de la simulación avanza, se ponen al día las variables:
time_advances += nearest_simulation_future_time - current_simulation_time;
last_time_updated = current_wall_time;
Con el mecanismo antedicho, una simulación funciona de manera semejante a un sistema real, en el sentido de que los eventos de ejecución son realizados en tiempo real en comparación a los puntos fijos de tiempo en simulaciones de eventos discretos (por eso es llamada simulacion de tiempo real basada en procesos). Las interacciones y las interferencias entre eventos, por lo tanto ocurren naturalmente como en sistemas verdaderos. Cuando no hay threads actualmente activos, el tiempo de ejecución realiza una operación de avance de tiempo al futuro más cercano en el cual por lo menos un thread puede ser activado. Esto preserva el comportamiento de sistemas verdaderos, y por lo tanto realza la fidelidad de la simulación, ya que siempre guarda el estado de la simulación.
La variable time_scale desempeña un papel
importante en tal metodología de simulación. Por ejemplo, si un
thread duerme durante 1 microsegundo, la simulación duerme
realmente durante (1.0e-6 * time_scale) segundos del tiempo del sistema.
Dando un valor apropiado al time_scale, por ejemplo, 1.0e6,
el tiempo real del sueño llega a ser factible en la resolución del
tiempo de la computadora en la cual la simulación corre. Por otra
parte, podemos ajustar el time_scale de modo que el
proceso retrase un acontecimiento en caídas de la simulación dentro
de la gama que deseamos modelar. Por ejemplo, si el proceso real
retrasa un acontecimiento 1 segundo en promedio, y nosotros
deseamos modelar el proceso retrasandolo 1 milisegundo, entonces nosotros
fijamos el time_scale = 1.0e3.
Observar que la simulación basada en eventos de tiempo discretos es
un caso especial de la simulación de tiempo real basada en procesos con
time_scale=infinito (todos los retardos de procesos llegan a ser cero en la simulación).
~ FIN ~