¿Se garantiza que JavaScript tiene un solo subproceso?

611

Se sabe que JavaScript tiene un solo subproceso en todas las implementaciones de navegador modernas, pero ¿se especifica eso en algún estándar o es solo por tradición? ¿Es totalmente seguro asumir que JavaScript siempre tiene un solo subproceso?

Egor Pavlikhin
fuente
25
En el contexto de los navegadores, probablemente. Pero algunos programas le permiten tratar JS como un idioma de nivel superior y proporcionar enlaces para otras bibliotecas C ++. Por ejemplo, flusspferd (enlaces de C ++ para JS - AWESOME BTW) estaba haciendo algunas cosas con JS multiproceso. Depende del contexto.
NG.
11
Esta es una lectura obligada: developer.mozilla.org/en/docs/Web/JavaScript/EventLoop
RickyA

Respuestas:

583

Buena pregunta. Me encantaría decir "sí". No puedo

Por lo general, se considera que JavaScript tiene un solo hilo de ejecución visible para los scripts (*), de modo que cuando se ingresa su script en línea, escucha de eventos o tiempo de espera, usted permanece completamente en control hasta que regrese desde el final de su bloque o función.

(*: ignorando la pregunta de si los navegadores realmente implementan sus motores JS usando un hilo del sistema operativo, o si WebWorkers introduce otros hilos de ejecución limitados).

Sin embargo, en realidad esto no es del todo cierto , de manera astuta y desagradable.

El caso más común son los eventos inmediatos. Los navegadores los activarán de inmediato cuando su código haga algo para causarlos:

var l= document.getElementById('log');
var i= document.getElementById('inp');
i.onblur= function() {
    l.value+= 'blur\n';
};
setTimeout(function() {
    l.value+= 'log in\n';
    l.focus();
    l.value+= 'log out\n';
}, 100);
i.focus();
<textarea id="log" rows="20" cols="40"></textarea>
<input id="inp">

Resultados en log in, blur, log outtodos excepto IE. Estos eventos no solo se disparan porque llamaste focus()directamente, podrían suceder porque llamastealert() , abriste una ventana emergente o cualquier otra cosa que mueva el foco.

Esto también puede dar lugar a otros eventos. Por ejemplo, agregue un i.onchangeoyente y escriba algo en la entrada antes de que la focus()llamada lo desenfoque, y el orden de registro es log in, change, blur, log out, excepto en Opera donde está log in, blur, log out, changee IE donde está (aún menos explicable) log in, change, log out, blur.

Del mismo modo, llamar click()a un elemento que lo proporciona llama al onclickcontrolador de inmediato en todos los navegadores (¡al menos esto es consistente!).

(Estoy usando las on...propiedades del controlador de eventos directos aquí, pero sucede lo mismo con addEventListenery attachEvent.)

También hay un montón de circunstancias en las que los eventos pueden activarse mientras su código está enhebrado, a pesar de que no ha hecho nada para provocarlo. Un ejemplo:

var l= document.getElementById('log');
document.getElementById('act').onclick= function() {
    l.value+= 'alert in\n';
    alert('alert!');
    l.value+= 'alert out\n';
};
window.onresize= function() {
    l.value+= 'resize\n';
};
<textarea id="log" rows="20" cols="40"></textarea>
<button id="act">alert</button>

Presiona alerty obtendrás un cuadro de diálogo modal. No se ejecutará más script hasta que descarte ese diálogo, ¿sí? No Cambie el tamaño de la ventana principal y accederá alert in, resize, alert outal área de texto.

Puede pensar que es imposible cambiar el tamaño de una ventana mientras está activo un cuadro de diálogo modal, pero no es así: en Linux, puede cambiar el tamaño de la ventana tanto como desee; en Windows no es tan fácil, pero puede hacerlo cambiando la resolución de la pantalla de una más grande a una más pequeña donde la ventana no se ajusta, lo que hace que cambie de tamaño.

Podrías pensar, bueno, es solo resize(y probablemente algunos más comoscroll ) que puede activarse cuando el usuario no tiene una interacción activa con el navegador porque el script está enhebrado. Y para ventanas individuales puede que tengas razón. Pero todo se va al bote tan pronto como estás haciendo scripts de ventanas cruzadas. Para todos los navegadores que no sean Safari, que bloquea todas las ventanas / pestañas / marcos cuando alguno de ellos está ocupado, puede interactuar con un documento desde el código de otro documento, ejecutándose en un hilo de ejecución separado y haciendo que cualquier controlador de eventos relacionado fuego.

Lugares donde se pueden generar eventos que puede generar que se generen mientras el script aún está enhebrado:

  • cuando las ventanas emergentes modales ( alert, confirm, prompt) están abiertas, pero en todos los navegadores Opera;

  • durante showModalDialogen los navegadores que lo admiten;

  • el cuadro de diálogo "Un script en esta página puede estar ocupado ...", incluso si elige dejar que el script continúe ejecutándose, permite que eventos como cambiar el tamaño y desenfoque se activen mientras el script está en medio de un Loop ocupado, excepto en Opera.

  • Hace un tiempo para mí, en IE con el complemento Sun Java, llamar a cualquier método en un applet podría permitir que se desencadenaran eventos y se volviera a ingresar el script. Esto siempre fue un error sensible al tiempo, y es posible que Sun lo haya solucionado desde entonces (ciertamente espero que sí).

  • Probablemente más. Ha pasado un tiempo desde que probé esto y los navegadores han ganado complejidad desde entonces.

En resumen, para la mayoría de los usuarios, la mayoría de las veces, JavaScript tiene un estricto subproceso de ejecución basado en eventos. En realidad, no tiene tal cosa. No está claro cuánto de esto es simplemente un error y cuánto diseño deliberado, pero si está escribiendo aplicaciones complejas, especialmente las de ventana cruzada / scripting de marcos, hay muchas posibilidades de que pueda morderlo, y en forma intermitente, formas difíciles de depurar.

Si lo peor llega a lo peor, puede resolver los problemas de concurrencia indirectando todas las respuestas a eventos. Cuando entra un evento, suéltelo en una cola y trate la cola en orden más adelante, en una setIntervalfunción. Si está escribiendo un marco que pretende ser utilizado por aplicaciones complejas, hacer esto podría ser un buen movimiento. postMessageTambién esperamos calmar el dolor de las secuencias de comandos de documentos cruzados en el futuro.

bobince
fuente
14
@JP: Personalmente, no quiero saber de inmediato porque significa que debo tener cuidado de que mi código sea reentrante, que llamar a mi código borroso no afectará el estado en el que se basa algún código externo. Hay demasiados casos en los que el desenfoque es un efecto secundario inesperado para atraparlos necesariamente. Y desafortunadamente, incluso si lo quieres, ¡no es confiable! IE se dispara blur después de que su código devuelve el control al navegador.
bobince
107
Javascript es de un solo subproceso. Detener su ejecución en alert () no significa que el hilo del evento deje de bombear eventos. Solo significa que su script está durmiendo mientras la alerta está en la pantalla, pero tiene que seguir bombeando eventos para dibujar la pantalla. Mientras hay una alerta activada, la bomba de eventos está funcionando, lo que significa que es completamente correcto seguir enviando eventos. En el mejor de los casos, esto demuestra un subproceso cooperativo que puede ocurrir en JavaScript, pero todo este comportamiento podría explicarse por una función que simplemente agrega un evento a la bomba de eventos para procesarlo en un momento posterior en lugar de hacerlo ahora.
chubbsondubs
34
Pero, recuerde que el enhebrado cooperativo sigue siendo de un solo hilo. Dos cosas no pueden suceder simultáneamente, que es lo que permite el subprocesamiento múltiple e inyecta no determinismo. Todo lo que se describió es determinista, y es un buen recordatorio sobre este tipo de problemas. Buen trabajo en el análisis @bobince
chubbsondubs
94
Chubbard tiene razón: JavaScript tiene un solo subproceso. Este no es un ejemplo de subprocesamiento múltiple, sino más bien el envío de mensajes sincrónicos en un solo hilo. Sí, es posible pausar la pila y hacer que continúe el despacho de eventos (por ejemplo, alert ()), pero los tipos de problemas de acceso que ocurren en entornos de subprocesos múltiples simplemente no pueden suceder; por ejemplo, nunca tendrá valores de cambio variable entre una prueba y una asignación inmediatamente posterior, porque su hilo no puede ser interrumpido arbitrariamente. Me temo que esta respuesta solo va a causar confusión.
Kris Giesing
19
Sí, pero dado que una función de bloqueo que espera la entrada del usuario puede ocurrir entre dos declaraciones, puede tener todos los problemas de consistencia que le brindan los subprocesos a nivel del sistema operativo. Si el motor de JavaScript realmente se ejecuta en múltiples hilos del sistema operativo es de poca relevancia.
bobince
115

Yo diría que sí, porque prácticamente todo el código javascript existente (al menos no trivial) se rompería si el motor javascript de un navegador lo ejecutara de forma asincrónica.

Agregue a eso el hecho de que HTML5 ya especifica Web Workers (una API explícita y estandarizada para código javascript de subprocesos múltiples) que la introducción de subprocesos múltiples en el Javascript básico no tendría sentido.

( Nota para otros comentaristas: aunque los setTimeout/setIntervaleventos de carga de solicitud HTTP (XHR) y los eventos de la interfaz de usuario (clic, enfoque, etc.) proporcionan una impresión cruda de multiproceso, todavía se ejecutan a lo largo de una sola línea de tiempo, uno en un tiempo, por lo que incluso si no conocemos su orden de ejecución de antemano, no hay necesidad de preocuparse por las condiciones externas que cambian durante la ejecución de un controlador de eventos, función temporizada o devolución de llamada XHR).

Már Örlygsson
fuente
21
Estoy de acuerdo. Si alguna vez se agrega multi-threading a Javascript en el navegador, será a través de alguna API explícita (por ejemplo, Web Workers) al igual que con todos los lenguajes imprescindibles. Esa es la única forma que tiene sentido.
Dean Harding
1
Tenga en cuenta que hay una sola. hilo principal de JS, PERO algunas cosas se ejecutan en el navegador en paralelo. No es solo una impresión de multiproceso. Las solicitudes se ejecutan realmente en paralelo. Los oyentes que define en JS se ejecutan uno por uno, pero las solicitudes son realmente paralelas.
Nux
16

Sí, aunque aún puede sufrir algunos de los problemas de la programación concurrente (principalmente condiciones de carrera) al usar cualquiera de las API asincrónicas como las devoluciones de llamada setInterval y xmlhttp.

gastador
fuente
10

Sí, aunque Internet Explorer 9 compilará su Javascript en un hilo separado en preparación para la ejecución en el hilo principal. Sin embargo, esto no cambia nada para ti como programador.

ChessWhiz
fuente
8

Diría que la especificación no impide que alguien cree un motor que ejecute javascript en varios subprocesos , lo que requiere que el código realice la sincronización para acceder al estado del objeto compartido.

Creo que el paradigma de no bloqueo de un solo subproceso surgió de la necesidad de ejecutar javascript en navegadores donde ui nunca debería bloquear.

Nodejs ha seguido el enfoque de los navegadores .

Sin embargo, el motor Rhino admite la ejecución de código js en diferentes subprocesos . Las ejecuciones no pueden compartir el contexto, pero pueden compartir el alcance. Para este caso específico, la documentación establece:

... "Rhino garantiza que los accesos a las propiedades de los objetos JavaScript son atómicos en todos los subprocesos, pero no ofrece más garantías para las secuencias de comandos que se ejecutan en el mismo ámbito al mismo tiempo. Si dos secuencias de comandos utilizan el mismo ámbito simultáneamente, las secuencias de comandos son responsable de coordinar los accesos a las variables compartidas ".

Al leer la documentación de Rhino, llego a la conclusión de que puede ser posible que alguien escriba una API de JavaScript que también genera nuevos hilos de JavaScript, pero la API sería específica de Rhino (por ejemplo, el nodo solo puede generar un nuevo proceso).

Me imagino que incluso para un motor que admite múltiples subprocesos en javascript debería haber compatibilidad con los scripts que no consideran multihilo o bloqueo.

Reconocer navegadores y nodos como lo veo es:

    1. ¿Se ejecuta todo el código js en un solo hilo ? : Si.
    1. ¿Puede el código js hacer que se ejecuten otros hilos ? : Si.
    1. ¿Pueden estos subprocesos mutar el contexto de ejecución de js ?: No. Pero pueden (directamente / indirectamente (?)) Agregarse a la cola de eventos desde la cual los oyentes pueden mutar el contexto de ejecución . Pero no se deje engañar, los oyentes corren atómicamente en el hilo principal nuevamente.

Entonces, en el caso de los navegadores y nodejs (y probablemente muchos otros motores), javascript no es multiproceso, pero los motores sí lo son .


Actualización sobre los trabajadores web:

La presencia de trabajadores web justifica aún más que javascript puede ser multiproceso, en el sentido de que alguien puede crear código en javascript que se ejecutará en un hilo separado.

Sin embargo: los trabajadores web no resuelven los problemas de los hilos tradicionales que pueden compartir el contexto de ejecución. Las reglas 2 y 3 anteriores todavía se aplican , pero esta vez el código creado por el usuario (escritor de código js) en javascript.

Lo único a considerar es la cantidad de hilos generados, desde una eficiencia (y no concurrencia) punto de vista de ). Vea abajo:

Sobre seguridad del hilo :

La interfaz Worker genera hilos reales a nivel del sistema operativo, y los programadores conscientes pueden estar preocupados de que la concurrencia pueda causar efectos "interesantes" en su código si no tiene cuidado.

Sin embargo, dado que los trabajadores web han controlado cuidadosamente los puntos de comunicación con otros hilos, en realidad es muy difícil causar problemas de concurrencia . No hay acceso a componentes que no sean seguros para subprocesos o al DOM. Y tiene que pasar datos específicos dentro y fuera de un hilo a través de objetos serializados. Por lo tanto, debe trabajar muy duro para causar problemas en su código.


PD

Además de la teoría, siempre esté preparado sobre posibles casos de esquina y errores descritos en la respuesta aceptada

Marinos An
fuente
7

JavaScript / ECMAScript está diseñado para vivir dentro de un entorno host. Es decir, JavaScript en realidad no hace nada menos que el entorno del host decida analizar y ejecutar un script determinado y proporcionar objetos de entorno que permitan que JavaScript sea realmente útil (como el DOM en los navegadores).

Creo que una función determinada o un bloque de script se ejecutará línea por línea y eso está garantizado para JavaScript. Sin embargo, quizás un entorno host podría ejecutar múltiples scripts al mismo tiempo. O bien, un entorno host siempre podría proporcionar un objeto que proporcione subprocesos múltiples. setTimeouty setIntervalson ejemplos, o al menos pseudoejemplos, de un entorno host que proporciona una forma de hacer algo de concurrencia (incluso si no es exactamente concurrencia).

Beto
fuente
7

En realidad, una ventana principal puede comunicarse con ventanas o marcos secundarios o hermanos que tienen sus propios subprocesos de ejecución en ejecución.

Kennebec
fuente
6

@Bobince está proporcionando una respuesta realmente opaca.

A partir de la respuesta de Már Örlygsson, Javascript siempre tiene un único subproceso debido a este simple hecho: todo en Javascript se ejecuta a lo largo de una sola línea de tiempo.

Esa es la definición estricta de un lenguaje de programación de un solo subproceso.

wle8300
fuente
4

No.

Voy contra la multitud aquí, pero tengan paciencia conmigo. Un solo script JS está destinado a ser efectivamente un solo subproceso, pero esto no significa que no se pueda interpretar de manera diferente.

Digamos que tienes el siguiente código ...

var list = [];
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Esto se escribe con la expectativa de que al final del ciclo, la lista debe tener 10000 entradas, que son el índice al cuadrado, pero la VM podría notar que cada iteración del ciclo no afecta al otro y reinterpretar usando dos hilos.

Primer hilo

for (var i = 0; i < 5000; i++) {
  list[i] = i * i;
}

Segundo hilo

for (var i = 5000; i < 10000; i++) {
  list[i] = i * i;
}

Estoy simplificando aquí, porque las matrices JS son más complicadas que los trozos de memoria tontos, pero si estas dos secuencias de comandos pueden agregar entradas a la matriz de una manera segura para los subprocesos, para cuando ambos terminen de ejecutar, habrá el mismo resultado que la versión de un solo subproceso.

Si bien no conozco ninguna máquina virtual que detecte código paralelo como este, parece probable que pueda surgir en el futuro para las máquinas virtuales JIT, ya que podría ofrecer más velocidad en algunas situaciones.

Llevando este concepto más allá, es posible que el código se pueda anotar para que la VM sepa qué convertir a código multiproceso.

// like "use strict" this enables certain features on compatible VMs.
"use parallel";

var list = [];

// This string, which has no effect on incompatible VMs, enables threading on
// this loop.
"parallel for";
for (var i = 0; i < 10000; i++) {
  list[i] = i * i;
}

Dado que los Web Workers están llegando a Javascript, es poco probable que este ... sistema más feo alguna vez exista, pero creo que es seguro decir que la tradición tiene un único subproceso.

max
fuente
2
Sin embargo, la mayoría de las definiciones de lenguaje están diseñadas para ser efectivamente de un solo subproceso, y establecen que se permite el subprocesamiento múltiple siempre que el efecto sea idéntico. (por ejemplo, UML)
jevon
1
Tengo que estar de acuerdo con la respuesta simplemente porque el ECMAScript actual no contiene ninguna disposición (aunque podría decirse que lo mismo puede decirse de C) para contextos de ejecución de ECMAScript concurrentes . Entonces, como esta respuesta, argumentaría que cualquier implementación que tenga hilos concurrentes que puedan modificar un estado compartido, es una extensión ECMAScript .
usuario2864740
3

Bueno, Chrome es multiproceso, y creo que cada proceso trata con su propio código Javascript, pero hasta donde el código lo sabe, es de "un solo subproceso".

No hay ningún tipo de soporte en Javascript para subprocesos múltiples, al menos no explícitamente, por lo que no hace la diferencia.

Francisco Soto
fuente
2

Probé el ejemplo de @bobince con ligeras modificaciones:

<html>
<head>
    <title>Test</title>
</head>
<body>
    <textarea id="log" rows="20" cols="40"></textarea>
    <br />
    <button id="act">Run</button>
    <script type="text/javascript">
        let l= document.getElementById('log');
        let b = document.getElementById('act');
        let s = 0;

        b.addEventListener('click', function() {
            l.value += 'click begin\n';

            s = 10;
            let s2 = s;

            alert('alert!');

            s = s + s2;

            l.value += 'click end\n';
            l.value += `result = ${s}, should be ${s2 + s2}\n`;
            l.value += '----------\n';
        });

        window.addEventListener('resize', function() {
            if (s === 10) {
                s = 5;
            }

            l.value+= 'resize\n';
        });
    </script>
</body>
</html>

Entonces, cuando presione Ejecutar, cierre la ventana emergente de alerta y haga un "hilo único", debería ver algo como esto:

click begin
click end
result = 20, should be 20

Pero si intenta ejecutar esto en Opera o Firefox estable en Windows y minimizar / maximizar la ventana con una ventana emergente de alerta en la pantalla, entonces habrá algo como esto:

click begin
resize
click end
result = 15, should be 20

No quiero decir que se trata de "subprocesos múltiples", pero se ejecutó un fragmento de código en un momento incorrecto y no esperaba esto, y ahora tengo un estado corrupto. Y mejor saber sobre este comportamiento.

Anton Alexandrenok
fuente
-4

Intente anidar dos funciones setTimeout entre sí y se comportarán con subprocesos múltiples (es decir, el temporizador externo no esperará a que se complete el interno antes de ejecutar su función).

James
fuente
55
Chrome hace esto de la manera correcta, no sé donde @James lo ve multiproceso ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(para el registro, tu dawg SIEMPRE debe ser primero, luego la salida del registro de la consola)
Tor Valamo