Red de conocimiento informático - Material del sitio web - ¿Qué papel juegan los hilos en JAVA?

¿Qué papel juegan los hilos en JAVA?

Este es un artículo muy clásico sobre hilos en javaeye. Está escrito de una manera fácil de entender y es adecuado para que lo lea cualquier experto en informática.

Sincronización de hilos

Podemos ejecutar varios programas informáticos en el ordenador. Cada programa en ejecución puede contener múltiples subprocesos que se ejecutan de forma independiente (Subprocesos).

Un hilo es una copia de un programa que se ejecuta de forma independiente y tiene su propia pila de tiempo de ejecución dedicada. Los subprocesos pueden compartir ciertos recursos con otros subprocesos, como memoria, archivos, bases de datos, etc.

Pueden surgir conflictos cuando varios subprocesos leen o escriben en el mismo recurso al mismo tiempo. En este momento necesitamos introducir la "sincronización" de subprocesos, lo que significa que los subprocesos deben llegar primero y luego llegar, en lugar de aglomerarse.

Sincronización es una palabra traducida del inglés sincronizar. No entiendo por qué usamos esta palabra tan engañosa. Dado que todo el mundo lo usa de esta manera, no tenemos más remedio que conformarnos con él.

El verdadero significado de sincronización de subprocesos es exactamente lo opuesto a su significado literal. Lo que realmente significa la sincronización de subprocesos es "poner en cola": poner en cola varios subprocesos para realizar operaciones en los recursos disfrutados uno por uno, en lugar de realizar todas las operaciones al mismo tiempo.

Entonces, lo primero que debemos recordar acerca de la sincronización de subprocesos es que la sincronización de subprocesos es una cola de subprocesos. La sincronización está en cola. El propósito de la sincronización de subprocesos es evitar que los subprocesos se ejecuten "sincrónicamente". Este es un trabalenguas muy aburrido.

Lo segundo que debemos recordar acerca de la sincronización de hilos es la palabra "***disfrute". Solo el acceso de lectura y escritura a los recursos que disfrutamos requiere sincronización. Si no es un recurso cubierto, no se requiere ninguna sincronización.

La tercera cosa que hay que recordar acerca de la sincronización de subprocesos es que sólo es necesario acceder sincrónicamente a las "variables". Si el recurso es fijo, entonces es una "constante" y no es necesario sincronizar los subprocesos que lo leen. Si al menos un hilo modifica el recurso del que disfruta ****, entonces se requiere sincronización entre hilos.

El cuarto punto a tener en cuenta sobre la sincronización de subprocesos es que varios subprocesos que acceden a recursos disfrutados pueden estar en el mismo código o pueden estar en códigos diferentes, independientemente de si los subprocesos están sincronizados. Se requiere entre subprocesos que ejecutan el mismo código y acceden a los mismos recursos mutables.

Para una mejor comprensión, aquí tienes algunos ejemplos.

Hay dos compradores con la misma descripción de trabajo y ambos siguen los siguientes pasos:

(1) Ir al mercado, buscar y comprar muestras potenciales.

(2) Regresar a la empresa y redactar un informe.

Aunque estas dos personas tienen el mismo contenido laboral y ambas necesitan comprar muestras, pueden comprar el mismo tipo de muestras, pero nunca comprarán las mismas muestras y no tienen ninguna *** * recursos entre ellos compartidos. Por lo tanto, pueden realizar sus respectivas funciones sin interferir entre sí.

Estos dos compradores equivalen a dos subprocesos; dos compradores que siguen los mismos pasos de trabajo equivalen a dos subprocesos que ejecutan el mismo código.

Aquí hay pasos adicionales para ambos compradores. Los compradores deben organizar su propio horario de trabajo de acuerdo con la información publicada en el "Tablero de anuncios" de la empresa.

Dos compradores pueden caminar hasta el tablón de anuncios al mismo tiempo y ver la información en el tablón de anuncios al mismo tiempo. Esto no es un problema en absoluto. Debido a que el tablón de anuncios es de solo lectura, ningún comprador cambiará la información en el tablón de anuncios.

A continuación se ha añadido un personaje. En ese momento, un administrador de la oficina también caminó hacia el tablón de anuncios y se preparó para cambiar la información en el tablón de anuncios.

Si el administrador llega primero al tablón de anuncios y está cambiando el contenido del mismo. En ese momento también llegaron dos compradores. Los dos compradores deben esperar a que el administrador complete los cambios antes de poder ver la información modificada.

Si llega el administrador, los dos compradores ya están revisando el tablón de anuncios.

Luego, el administrador debe esperar a que los dos compradores registren la información actual antes de escribir información nueva.

En ambos casos es necesario sincronizar el acceso de administrador y comprador al tablón de anuncios. Esto se debe a que uno de los hilos (el administrador) modificó *** para disfrutar del recurso (el tablón de anuncios). Podemos ver que el flujo de trabajo del administrador es completamente diferente del flujo de trabajo del comprador (código de ejecución), pero dado que acceden al mismo recurso variable de disfrute sexual (tablón de anuncios), deben sincronizarse.

Bloqueo de sincronización

Hablamos anteriormente sobre por qué es necesaria la sincronización de subprocesos. Veamos cómo realizar la sincronización de subprocesos.

La idea básica de la sincronización de subprocesos es relativamente fácil de entender. Podemos colocar un candado en un recurso que disfrutamos mucho, y el candado solo tiene una llave. Cualquiera que sea el hilo que obtenga la clave tendrá acceso al recurso que disfrutamos.

En la vida también podemos encontrarnos con este tipo de ejemplos. Algunos supermercados disponen de taquillas automáticas en el exterior. Cada casillero tiene candado y llave. Las personas pueden usar estos casilleros con llave, poner cosas en el casillero, cerrar el casillero y quitar la llave. Luego, el casillero se cierra con llave y nadie más puede acceder al casillero. (Por supuesto, las llaves reales de los casilleros se pueden quitar y copiar, así que no deje objetos de valor en los casilleros del supermercado. Es por eso que muchos supermercados usan cerraduras con código electrónico).

El modelo de bloqueo de sincronización de subprocesos parece muy intuitivo. Sin embargo, hay una pregunta seria que aún no se ha resuelto: ¿dónde se debe agregar el bloqueo de sincronización?

Por supuesto, se agrega al recurso ****-disfrute. Los lectores de Kuairen Kuaiyu definitivamente encontrarán la respuesta más rápido que usted.

Sí, por supuesto agregaremos bloqueos de sincronización a los recursos habilitados para **** siempre que sea posible. Algunos recursos que admiten **** (como sistemas de archivos, sistemas de bases de datos, etc.) proporcionan por sí mismos buenos mecanismos de bloqueo de sincronización. No necesitamos agregar bloqueos a estos recursos, ellos mismos tienen bloqueos.

Pero en la mayoría de los casos, los recursos de atención a los que accedemos en nuestro código son objetos de atención relativamente simples. No hay lugar para que guardemos ninguno de estos objetos.

Los lectores pueden hacer esta sugerencia: ¿Por qué no agregar una nueva área dedicada a bloquear el interior de cada objeto? Este diseño es ciertamente factible en teoría. El problema es que la sincronización de subprocesos no es común. Si se abre un espacio de bloqueo dentro de todos los objetos debido a un evento de probabilidad tan pequeño, provocará una gran pérdida de espacio. No vale la pena.

Por lo tanto, la filosofía de diseño de los lenguajes de programación modernos es establecer bloqueos de sincronización en fragmentos de código. Para ser precisos, el bloqueo de sincronización se agregó a "Acceder a los recursos del segmento de código propiedad de ***". Es importante recordar que los bloqueos de sincronización se colocan en segmentos de código.

Los bloqueos de sincronización en fragmentos son una buena manera de resolver el problema de desperdicio de espacio mencionado anteriormente. Pero aumenta la complejidad del modelo y nos hace más difícil entenderlo.

Ahora, aprendamos más sobre el modelo de sincronización de subprocesos a través de "Bloqueos de sincronización en fragmentos".

En primer lugar, hemos solucionado el problema de la ubicación del bloqueo de sincronización. Hemos determinado que el bloqueo de sincronización no se coloca en el recurso del que disfruta ****, sino en el segmento de código que accede al recurso del que disfruta ****.

La siguiente pregunta que debemos abordar es qué tipo de bloqueo debemos colocar en el segmento de código. Esta pregunta es lo más destacado de lo más destacado. Debemos prestar especial atención a esto: diferentes segmentos de código que acceden al mismo recurso disfrutado deben agregarles el mismo bloqueo de sincronización, si se les agregan diferentes bloqueos de sincronización, entonces no se sincronizarán en absoluto, y eso; tampoco tiene ningún sentido.

Esto significa que el bloqueo de sincronización en sí también debe ser un enlace entre varios subprocesos.

Palabra clave de sincronización en lenguaje Java

Para una mejor comprensión, eche un vistazo a algunos ejemplos de fragmentos de código de sincronización.

El modelo de bloqueo de sincronización es el mismo en todos los idiomas. Sólo la expresión es ligeramente diferente. Aquí tomamos como ejemplo el lenguaje Java más popular. El lenguaje Java utiliza la palabra clave sincronizada para agregar bloqueos a segmentos de código. La sintaxis completa se expresa como:

sincronizado(bloqueo sincronizado) {

// Acceda a recursos que disfrutamos, segmentos de código que deben sincronizarse

}

Lo más importante a tener en cuenta aquí es que el bloqueo de sincronización en sí debe ser un objeto muy disfrutado.

... f1() {

Object lock1 = new Object(); // Genera un bloqueo de sincronización

synchronized(lock1){

// Fragmento A

// Acceso al recurso ****-prestado recurso1

/// Requiere sincronización

}

/// Requiere sincronización

}

...

}

El código anterior no tiene ningún sentido. Porque el bloqueo de sincronización se genera dentro del cuerpo de la función. Cada hilo que llama a este código genera un nuevo bloqueo de sincronización. Luego, se utilizan diferentes bloqueos de sincronización entre varios subprocesos. Esto anula por completo el propósito de la sincronización.

El código de sincronización debe escribirse de la siguiente forma para que tenga sentido.

objeto final estático público lock1 = new Object();

...f1() {

sincronizado(lock1){ // lock1 es sincronización pública Bloquear

// Fragmento de código A

// Acceso al recurso ****-disfrutado recurso1

// Se requiere sincronización

}

No es necesario declarar el bloqueo de sincronización como un bloqueo estático o público, pero debe asegurarse de utilizar el mismo bloqueo de sincronización entre el código de sincronización relacionado.

Dicho esto, debes preguntarte qué es exactamente el bloqueo de sincronización. ¿Por qué puedo declarar un Objeto aleatorio y usarlo como bloqueo de sincronización?

En Java, el concepto de bloqueo de sincronización es así. Cualquier referencia de objeto se puede utilizar como bloqueo de sincronización. Podemos entender una referencia de objeto como la dirección de memoria del objeto en el sistema de asignación de memoria. Por lo tanto, para garantizar que se utilice el mismo bloqueo de sincronización entre segmentos de código sincronizados, debemos asegurarnos de que las palabras clave de sincronización de estos segmentos de código sincronizados utilicen la misma referencia de objeto, es decir, la misma dirección de memoria. Es por eso que utilicé la palabra clave final al declarar lock1 en el código anterior para garantizar que la referencia del objeto de lock1 permanezca sin cambios durante la operación del sistema.

Es posible que algunos lectores curiosos quieran profundizar en la mecánica real de la sincronización, que se explica en detalle en la Especificación de la máquina virtual Java (puede buscar en Google usando la palabra clave "JVM Spec"). Hay una explicación detallada de la palabra clave sincronizada en la Especificación de la máquina virtual Java (google "JVM Spec"). sincronizado se compila en pares de instrucciones, como monitor enter, ... monitor exit, etc. El monitor es el bloqueo de sincronización real. Cada referencia de objeto corresponde conceptualmente a un monitor.

Estos detalles de implementación no son la clave para comprender el modelo de bloqueo sincrónico. Veamos algunos ejemplos para profundizar nuestra comprensión del modelo de bloqueo de sincronización.

objeto final estático público lock1 = new Object();

... f1() {

sincronizado(lock1){ // lock1 es sincronización pública Bloquear

// Fragmento de código A

// Acceder al recurso ****-disfrutado recurso1

// Necesita sincronizarse

}

}

... f2() {

synchronized(lock1){ // lock1 es un bloqueo de sincronización público

/ Fragmento de código B <

// Acceso al recurso ****-disfrutado recurso1

// Requiere sincronización

}

.. .

En el código anterior, el segmento de código A y el segmento de código B están sincronizados. Esto se debe a que utilizan el mismo bloqueo de sincronización lock1.

Si 10 subprocesos ejecutan el segmento de código A al mismo tiempo y 20 subprocesos ejecutan el segmento de código B al mismo tiempo, entonces los 30 subprocesos se sincronizan entre sí.

Estos 30 subprocesos compiten por un bloqueo de sincronización (lock1). Al mismo tiempo, solo un subproceso puede obtener la propiedad de lock1 y solo un subproceso puede ejecutar el segmento de código A o el segmento de código B.

Hay varias colas de subprocesos colgando debajo de cada bloqueo de sincronización, incluida la cola lista, la cola en espera, etc. Por ejemplo, la cola lista para el bloqueo 1 podría denominarse Bloqueo 1 - Cola lista y cada cola podría contener varios subprocesos suspendidos.

Tenga en cuenta que un hilo que no pueda competir por un bloqueo de sincronización ingresará a la cola lista para ese bloqueo de sincronización, no a la cola de espera que discutiremos más adelante. Los subprocesos en la cola listos están listos para competir por los bloqueos de sincronización y están listos para ejecutarse. Los subprocesos en la cola de espera deben esperar la notificación de la señal antes de poder pasar a la cola de listos y estar listos para ejecutarse.

El hilo que adquiere con éxito el bloqueo de sincronización lo liberará después de ejecutar el segmento de código de sincronización. Otros subprocesos en la cola de bloqueo de sincronización listos ingresarán a la siguiente ronda de competencia de bloqueo de sincronización. Los subprocesos exitosos seguirán ejecutándose, mientras que los subprocesos fallidos permanecerán en la cola de listos.

La sincronización de subprocesos es, por tanto, una operación que consume muchos recursos. Necesitamos intentar controlar el alcance del segmento de código de sincronización. Cuanto menor sea el alcance del fragmento de sincronización, mejor. Usamos el término "granularidad de sincronización" para referirnos al alcance de un fragmento de código sincronizado.

Granularidad de sincronización

En Java, podemos agregar la palabra clave de sincronización directamente en la definición de la función.

Por ejemplo

... sincronizado... f1() {

// fragmento f1

}

Este código es equivalente a

... f1() {

sincronizado(esto){ // El bloqueo de sincronización es el objeto en sí

// f1 Fragmento

}

... f1() {

sincronizado(esto){ // El bloqueo de sincronización es el objeto mismo

/ /f1 fragmento

}

...

}

El mismo principio se aplica a estático (estático) funciones

Por ejemplo.

...estático sincronizado... f1() {

// segmento de código f1

}

Este código es equivalente to in

...static ... f1() {

synchronized(Class.forName(...)){ // El bloqueo de sincronización es la definición de clase misma

// fragmento de código f1

}

...

}

Pero intentaremos evitar este tipo de código directamente en Agregue métodos diferidos sincronizados a las definiciones de funciones. Porque queremos controlar la granularidad de la sincronización. Cuanto más pequeño sea el segmento de sincronización, mejor y cuanto más pequeño sea el rango de control de sincronización, mejor.

No solo debemos trabajar duro para acortar la longitud de los segmentos del código de sincronización, sino que también debemos subdividir cuidadosamente los bloqueos de sincronización.

Por ejemplo, el siguiente código

public static final Object lock1 = new Object();

... f1() {

sincronizado(lock1){ // lock1 es un bloqueo de sincronización público

// Fragmento de código A

// Acceso al recurso ****-disfrutado1

// Necesita sincronizar

}

}

... f2() {

sincronizado(lock1){ // lock1 es un bloqueo de sincronización pública

// Segmento de código B

/ Acceso ****-disfrutado recurso1

// Sincronización requerida

}

}

... f3() {

synchronized(lock1){ // lock1 es un bloqueo de sincronización público

// fragmento C

// Acceso ****-disfrutado recurso2

// Requiere sincronización

}

}

... f4() {

sincronizado(lock1){ // lock1 es un bloqueo de sincronización público

... lock1 es un bloqueo de sincronización público

// Segmento de código D

// Acceso ****-disfrutado recurso2

// Sincronización requerida

}

}

Los cuatro códigos de sincronización anteriores utilizan el mismo bloqueo de sincronización lock1. Todos los subprocesos que llaman a estos códigos deben competir por el mismo bloqueo de sincronización lock1.

Después de un análisis cuidadoso, descubrimos que esto es innecesario.

Dado que el fragmento de código A de f1() y el fragmento de código B de f2() acceden al recurso 1, mientras que el fragmento de código C de f3() y el fragmento de código D de f4() acceden al recurso 2, por lo que no No tienes que competir por el mismo bloqueo de sincronización lock1.

El código de f4() se puede modificar como:

public static final Object lock2 = new Object();

... f3() {

sincronizado( lock2){ // lock2 es un bloqueo de sincronización público

// Fragmento de código C

/ Acceso al recurso disfrutado ****2

/ / Se requiere sincronización

}

}

... f4() {

sincronizado(lock2){ // lock2 es un bloqueo de sincronización público <

// Fragmento de código D

// Acceso ****-disfrutado recurso2

// Requiere sincronización

}

}

Sincronizado(lock2){

De esta manera, f1() y f2() competirán por el bloqueo 1 y f3( ) y f4() competirán por el bloqueo 2. Esto reduce la probabilidad de contención de bloqueo de sincronización, reduciendo así la sobrecarga del sistema.

Semaphore

El modelo de bloqueo de sincronización es solo el modelo de sincronización más simple. Solo un hilo puede ejecutar código sincronizado al mismo tiempo.

A veces, necesitamos lidiar con modelos de sincronización más complejos, como el modelo de productor/consumidor, el modelo de sincronización de lectura/escritura, etc. En este caso, el modelo de bloqueo de sincronización no es suficiente. Necesitamos un nuevo modelo. Este es el modelo de señalización que vamos a discutir.

El modelo de semáforo funciona de la siguiente manera: un hilo puede detenerse y esperar notificaciones del semáforo mientras se está ejecutando; luego, el hilo ingresa a la cola de espera del semáforo y luego continúa ejecutándose hasta que se recibe una notificación; recibió.

En muchos lenguajes, los bloqueos de sincronización están representados por objetos especiales, a menudo llamados monitores.

Del mismo modo, en muchos idiomas, los transmisores de señales suelen estar representados por objetos especiales, como mutex y transmisores de señales.

El modelo de señal es mucho más complejo que el modelo de bloqueo de sincronización. En algunos sistemas, los semáforos pueden incluso sincronizarse entre procesos. Otros semáforos incluso tienen capacidades de conteo para controlar la cantidad de subprocesos que se ejecutan simultáneamente.

No tenemos que considerar modelos tan complejos. Todos estos modelos complejos tienen su origen en el modelo más básico. Una vez que domine el modelo de señal básico, el modelo "esperar/notificar", la complejidad del modelo se vuelve evidente.

Tome Java como ejemplo. Los conceptos de bloqueos de sincronización y semáforos en Java son muy vagos; no existen términos de objeto específicos para bloqueos de sincronización y semáforos, y solo hay dos palabras clave relacionadas con los bloqueos de sincronización: volátil. y sincronizado.

Estas palabras clave se utilizan en el lenguaje de programación Java, pero no se utilizan en el lenguaje de programación Java.

Esta ambigüedad conduce a confusión conceptual pero también evita todos los malentendidos asociados con términos como Monitor, Mutex, Semphore, etc. En lugar de obsesionarnos con la terminología, podemos centrarnos en comprender cómo funciona realmente.

En Java, cualquier referencia de objeto se puede utilizar como bloqueo de sincronización. Asimismo, cualquier referencia de objeto puede actuar como señal.

El método wait() del objeto object se usa para esperar notificaciones, mientras que el método notify() del objeto object se usa para enviar notificaciones.

La llamada específica es la siguiente

(1) Esperar la notificación de un determinado semáforo

public static final Object signal = new Object();

... f1() {

synchronized( singal) { // Primero necesitamos obtener este semáforo. Esta señal también es un bloqueo de sincronización

//Solo después de adquirir con éxito la señal, la señal y el bloqueo de sincronización, podemos ingresar este código

signal.wait(); aquí Señal. Este hilo entrará en la cola de espera de la señal

// Pobrecito. La señal ganada con tanto esfuerzo acaba de ser abandonada

// Esperando una notificación para ingresar a la cola Listo desde la cola de espera

// Una vez que ingresa a la cola Listo, estará más cerca de la Núcleo de CPU Paso uno y tendrá la oportunidad de continuar ejecutando el código a continuación.

// Antes de continuar ejecutando el código a continuación, aún debe competir por el bloqueo de sincronización de señal. Este es un gran proyecto.

...

}

...

}

Cabe señalar que en el código anterior El significado de signal.wait(). signal.wait() puede dar lugar fácilmente a malentendidos. signal.wait () no significa que la señal comienza a esperar, pero Wait () no significa que la señal está esperando, sino que el hilo actual que ejecuta este código está esperando el objeto de señal, es decir, está en espera. cola del objeto de señal.

(2) Notificar un determinado semáforo

... f2() {

synchronized(singal) { // Primero, también debemos obtener el semáforo . También es un bloqueo de sincronización.

// Solo después de adquirir con éxito singal, un semáforo y un bloqueo de sincronización, podemos ingresar este código

signal.notify() // Aquí, notificaremos a la señal Un hilo. en la cola de llamadas pendientes.

// Si hay un hilo esperando la notificación, el hilo entrará en la cola listo

// Pero el hilo seguirá teniendo el bloqueo de sincronización en la señal, y el hilo continuará ejecutándose

// Oye, aunque el hilo tuvo la amabilidad de notificar a otros hilos,

// el hilo no fue lo suficientemente noble como para abandonar el bloqueo de sincronización en sus manos. Bloqueo de sincronización

//Este hilo continúa ejecutando el siguiente código

...

}

}

Tenga en cuenta que signal.notify() significa que signal.notify() no notifica al objeto de señal en sí. En cambio, notifica a otros subprocesos que están esperando el semáforo de señal.

Lo anterior es el uso básico de esperar() y notificar() para objetos.

De hecho, wait() también puede definir el tiempo de espera, de modo que cuando un hilo espera un registro de señal en la cola de suspensión durante el tiempo suficiente, ya no esperará y se eliminará de la lista pendiente; cola e ingrese a la cola lista.

Además, existe un método notifyAll() para notificar a todos los subprocesos en la cola sobre llamadas pendientes.

Estos son detalles y no afectarán la situación general.

Hilo verde

El hilo verde es un concepto relativo a los hilos del sistema operativo (hilos locales).

Los subprocesos del sistema operativo (subprocesos locales) se refieren a subprocesos dentro del programa que en realidad están asignados a subprocesos del sistema operativo, y el sistema operativo ejecuta y programa los subprocesos.

Subproceso verde significa que los subprocesos dentro del programa en realidad no están asignados a los subprocesos del sistema operativo, sino que están programados por la propia plataforma de ejecución del lenguaje.

Los subprocesos en la versión actual del lenguaje Python se asignan a subprocesos del sistema operativo. Los subprocesos en la versión actual del lenguaje Ruby son subprocesos verdes y no se pueden asignar a subprocesos del sistema operativo, por lo que los subprocesos en el lenguaje Ruby se ejecutan más lento.

¿Significa esto que los subprocesos verdes son más lentos que los subprocesos del sistema operativo? Por supuesto que no. De hecho, puede ocurrir lo contrario. Ruby es un caso especial. El programador de subprocesos no es complejo.

Actualmente, el modelo de implementación popular de hilos son los hilos verdes. Por ejemplo, Python sin pila introduce el concepto más ligero de subprocesos verdes. En lo que respecta a la programación concurrente con subprocesos, es mejor que Python en términos de velocidad de ejecución y carga concurrente.

Otro ejemplo más famoso es ErLang (un lenguaje de código abierto desarrollado por Ericsson).

El concepto de hilo verde de ErLang es muy radical: el hilo de ErLang no se llama hilo, sino proceso, que se confunde fácilmente con un proceso. Aquí debemos hacer una distinción.

Los procesos de ErLang no necesitan sincronizarse entre sí en absoluto. Esto se debe a que todas las variables en el lenguaje ErLang son finales y no permiten ningún cambio en el valor de la variable. Por lo tanto, no se requiere ninguna sincronización.

Otro beneficio de las variables finales es que no hay referencia cruzada entre objetos, ni es posible formar una asociación circular; la asociación entre objetos es unidireccional y en forma de árbol. Por lo tanto, el algoritmo de recolección de basura de la memoria también es muy eficiente. Esto permite a ErLang lograr un tiempo real suave. Esto no es fácil para los lenguajes que admiten la recolección de basura en la memoria.