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?
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?
Respuestas:
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 out
todos 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.onchange
oyente 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, change
e 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 onclick
controlador 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 addEventListener
y 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 alert
y 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 out
al á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 showModalDialog
en 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 setInterval
función. Si está escribiendo un marco que pretende ser utilizado por aplicaciones complejas, hacer esto podría ser un buen movimiento. postMessage
También esperamos calmar el dolor de las secuencias de comandos de documentos cruzados en el futuro.
blur
después de que su código devuelve el control al navegador.
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/setInterval
eventos 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).
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:
Entonces, en el caso de los navegadores y nodejs (y probablemente muchos otros motores), javascript no es multiproceso, pero los motores sí lo son .
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:
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.
Además de la teoría, siempre esté preparado sobre posibles casos de esquina y errores descritos en la respuesta aceptada
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. setTimeout
y setInterval
son ejemplos, o al menos pseudoejemplos, de un entorno host que proporciona una forma de hacer algo de concurrencia (incluso si no es exactamente concurrencia).
@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.
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.
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.
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.
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).
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)