JavaScript è garantito per essere single thread?


611

JavaScript è noto per essere a thread singolo in tutte le implementazioni di browser moderne, ma è specificato in uno standard o è solo per tradizione? È assolutamente sicuro supporre che JavaScript sia sempre a thread singolo?


25
Nel contesto dei browser, probabilmente. Ma alcuni programmi ti permettono di trattare JS come un lang di alto livello e fornire collegamenti per altre librerie C ++. Ad esempio, flusspferd (collegamenti C ++ per JS - AWESOME BTW) stava facendo alcune cose con JS multithread. Dipende dal contesto.
GN.

11
Questo è
assolutamente da

Risposte:


583

Questa è una bella domanda. Mi piacerebbe dire "sì". Non posso.

JavaScript viene generalmente considerato come un singolo thread di esecuzione visibile agli script (*), in modo che quando si inseriscono lo script in linea, il listener di eventi o il timeout, si rimane completamente sotto controllo fino a quando non si ritorna dalla fine del blocco o della funzione.

(*: ignorando la domanda se i browser implementano davvero i loro motori JS usando un thread del sistema operativo o se WebWorker introduce altri thread di esecuzione limitati.)

Tuttavia, in realtà questo non è del tutto vero , in modi subdoli e cattivi.

Il caso più comune sono gli eventi immediati. I browser li attiveranno immediatamente quando il codice fa qualcosa per causarli:

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">

Risultati in log in, blur, log outtutto tranne IE. Questi eventi non si attivano solo perché hai chiamato focus()direttamente, potrebbero accadere perché hai chiamato alert()o aperto una finestra pop-up o qualsiasi altra cosa che sposta lo stato attivo.

Ciò può comportare anche altri eventi. Ad esempio, aggiungi un i.onchangeascoltatore e digita qualcosa nell'input prima che la focus()chiamata lo sfoci, e l'ordine del registro è log in, change, blur, log out, tranne in Opera dove è log in, blur, log out, changee IE dove è (ancora meno esplicitamente) log in, change, log out, blur.

Allo stesso modo invocare click()un elemento che lo fornisce chiama onclickimmediatamente il gestore in tutti i browser (almeno questo è coerente!).

(Sto usando le on...proprietà del gestore di eventi diretti qui, ma lo stesso succede con addEventListenere attachEvent.)

Ci sono anche molte circostanze in cui gli eventi possono essere attivati ​​mentre il tuo codice è inserito, nonostante tu non abbia fatto nulla per provocarlo. Un esempio:

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>

Premi alerte otterrai una finestra di dialogo modale. Niente più script viene eseguito fino a quando non chiudi quel dialogo, sì? No. Ridimensiona la finestra principale e alert in, resize, alert outentrerai nell'area di testo.

Potresti pensare che sia impossibile ridimensionare una finestra mentre è attiva una finestra di dialogo modale, ma non è così: in Linux, puoi ridimensionare la finestra quanto vuoi; su Windows non è così semplice, ma puoi farlo cambiando la risoluzione dello schermo da una più grande a una più piccola in cui la finestra non si adatta, causando il ridimensionamento.

Potresti pensare, beh, è ​​solo resize(e probabilmente qualche altro tipo scroll) che può attivarsi quando l'utente non ha un'interazione attiva con il browser perché lo script è thread. E per singole finestre potresti avere ragione. Ma tutto va a segno non appena esegui scripting cross-window. Per tutti i browser diversi da Safari, che blocca tutte le finestre / schede / frame quando uno di essi è occupato, è possibile interagire con un documento dal codice di un altro documento, eseguendolo in un thread di esecuzione separato e facendo sì che tutti i gestori di eventi correlati fuoco.

Luoghi in cui è possibile generare eventi che possono essere generati mentre lo script è ancora in thread:

  • quando il popup modali ( alert, confirm, prompt) sono aperti, in tutti i browser, ma Opera;

  • durante i showModalDialogbrowser che lo supportano;

  • la finestra di dialogo "Uno script in questa pagina potrebbe essere occupato ...", anche se si sceglie di consentire l'esecuzione dello script, consente di attivare eventi come ridimensionamento e sfocatura e di gestirli anche quando lo script si trova nel mezzo di un occupato-loop, tranne in Opera.

  • qualche tempo fa per me, in IE con il plug-in Sun Java, la chiamata a qualsiasi metodo su un'applet poteva consentire agli eventi di attivarsi e al reinserimento dello script. Questo è sempre stato un bug sensibile ai tempi, ed è possibile che Sun l'abbia corretto da allora (lo spero certamente).

  • probabilmente di più. È passato un po 'di tempo da quando l'ho provato e da allora i browser hanno acquisito complessità.

In breve, JavaScript sembra che la maggior parte degli utenti disponga di un singolo thread di esecuzione basato sugli eventi. In realtà, non ha nulla del genere. Non è chiaro quanto di tutto ciò sia semplicemente un bug e quanta progettazione deliberata, ma se stai scrivendo applicazioni complesse, in particolare quelle cross-window / frame-scripting, c'è ogni possibilità che ti possa mordere - e in modo intermittente, modi difficili da eseguire il debug.

Se il peggio arriva al peggio, puoi risolvere i problemi di concorrenza indirizzando tutte le risposte agli eventi. Quando arriva un evento, rilascialo in una coda e gestisci la coda in un secondo momento, in una setIntervalfunzione. Se stai scrivendo un framework che intendi essere utilizzato da applicazioni complesse, farlo potrebbe essere una buona mossa. postMessagesperiamo anche di lenire il dolore degli script tra documenti in futuro.


14
@JP: Personalmente non voglio saperlo subito perché significa che devo stare attento al rientro del mio codice, che chiamare il mio codice di sfocatura non influirà sullo stato su cui si basa un codice esterno. Ci sono troppi casi in cui la sfocatura è un effetto collaterale inaspettato per catturare necessariamente tutti. E purtroppo anche se si fa lo vogliono, non è affidabile! IE si attiva blur dopo che il codice ha restituito il controllo al browser.
bobince,

107
Javascript è a thread singolo. Arrestare la tua esecuzione su alert () non significa che il thread degli eventi arresta il pumping degli eventi. Significa solo che la tua sceneggiatura sta dormendo mentre l'avviso è sullo schermo, ma deve continuare a pompare eventi per poter disegnare lo schermo. Mentre è attivo un avviso, la pompa degli eventi è in esecuzione, il che significa che è del tutto corretto continuare a inviare eventi. Nella migliore delle ipotesi questo dimostra un thread cooperativo che può avvenire in JavaScript, ma tutto questo comportamento potrebbe essere spiegato da una funzione semplicemente aggiungendo un evento alla pompa degli eventi per essere elaborato in un secondo momento anziché farlo ora.
Chubbsondubs,

34
Ma ricorda che il threading cooperativo è ancora single thread. Non possono accadere contemporaneamente due cose, che è ciò che il multi-threading consente e inietta non determinismo. Tutto ciò che è stato descritto è deterministico ed è un buon promemoria su questo tipo di problemi. Ottimo lavoro sull'analisi @bobince
chubbsondubs,

94
Chubbard ha ragione: JavaScript è a thread singolo. Questo non è un esempio di multithreading, ma piuttosto l'invio di messaggi sincroni in un singolo thread. Sì, è possibile mettere in pausa lo stack e far continuare l'invio di eventi (ad esempio alert ()), ma i tipi di problemi di accesso che si verificano in veri ambienti multithread non possono semplicemente accadere; ad esempio, non avrai mai una variabile che cambia i valori tra un test e un'assegnazione immediatamente successiva, perché il tuo thread non può essere interrotto arbitrariamente. Temo che questa risposta causerà solo confusione.
Kris Giesing,

19
Sì, ma dato che una funzione di blocco che attende l'input dell'utente può avvenire tra due istruzioni qualsiasi, hai potenzialmente tutti i problemi di coerenza che ti portano i thread a livello di sistema operativo. Il fatto che il motore JavaScript funzioni effettivamente in più thread del sistema operativo è di scarsa rilevanza.
Bobince

115

Direi di sì, perché praticamente tutto il codice javascript esistente (almeno non banale) si spezzerebbe se il motore javascript di un browser dovesse eseguirlo in modo asincrono.

Aggiungete a ciò il fatto che HTML5 specifica già Web Workers (un'API esplicita e standardizzata per il codice javascript multi-threading) che introduce il multi-threading nel Javascript di base sarebbe quasi inutile.

( Nota per gli altri commentatori: anche se gli setTimeout/setIntervaleventi di onload della richiesta HTTP (XHR) e gli eventi dell'interfaccia utente (clic, focus, ecc.) Forniscono un'impressione rozza di multi-thread - sono ancora tutti eseguiti lungo una singola sequenza temporale - uno a un tempo - quindi anche se non conosciamo in anticipo il loro ordine di esecuzione, non è necessario preoccuparsi delle condizioni esterne che cambiano durante l'esecuzione di un gestore eventi, di una funzione a tempo o di un callback XHR.)


21
Sono d'accordo. Se il multi-threading viene mai aggiunto a Javascript nel browser, avverrà tramite alcune API esplicite (ad esempio Web Workers) proprio come in tutte le lingue imperative. Questo è l'unico modo che ha senso.
Dean Harding,

1
Si noti che esiste un singolo. thread JS principale, MA alcune cose vengono eseguite nel browser in parallelo. Non è solo un'impressione di multi-thread. Le richieste vengono effettivamente eseguite in parallelo. I listener definiti in JS vengono eseguiti uno a uno, ma le richieste sono veramente parallele.
Nux,

16

Sì, sebbene si possano ancora riscontrare alcuni problemi della programmazione concorrente (principalmente le condizioni di gara) quando si utilizzano le API asincrone come callbacks setInterval e xmlhttp.


10

Sì, anche se Internet Explorer 9 compilerà il tuo Javascript su un thread separato in preparazione per l'esecuzione sul thread principale. Questo non cambia nulla per te come programmatore, però.


8

Direi che la specifica non impedisce a qualcuno di creare un motore che esegue javascript su più thread , richiedendo al codice di eseguire la sincronizzazione per accedere allo stato dell'oggetto condiviso.

Penso che il paradigma non bloccante a thread singolo sia nato dalla necessità di eseguire javascript nei browser in cui non dovresti mai bloccare.

Nodejs ha seguito l' approccio dei browser .

Il motore di Rhino tuttavia supporta l'esecuzione del codice js in thread diversi . Le esecuzioni non possono condividere il contesto, ma possono condividere l'ambito. Per questo caso specifico la documentazione afferma:

... "Rhino garantisce che gli accessi alle proprietà degli oggetti JavaScript sono atomici tra i thread, ma non fornisce ulteriori garanzie per gli script che si eseguono nello stesso ambito contemporaneamente. Se due script usano lo stesso ambito contemporaneamente, gli script sono responsabile del coordinamento degli accessi alle variabili condivise ".

Dalla lettura della documentazione di Rhino, concludo che può essere possibile per qualcuno scrivere un'API javascript che genera anche nuovi thread javascript, ma l'API sarebbe specifico di Rhino (ad esempio il nodo può generare solo un nuovo processo).

Immagino che anche per un motore che supporta più thread in JavaScript dovrebbe esserci compatibilità con gli script che non considerano il multi-threading o il blocco.

Concearning browser e nodejs come la vedo io sono:

    1. È tutto js codice eseguito in un singolo thread ? : Sì.
    1. Il codice js può causare l'esecuzione di altri thread ? : Sì.
    1. Questi thread possono mutare il contesto di esecuzione di js ?: No. Ma possono (direttamente / indirettamente (?)) Aggiungere alla coda degli eventi da cui gli ascoltatori possono mutare il contesto di esecuzione . Ma non lasciarti ingannare, gli ascoltatori corrono di nuovo atomicamente sul thread principale .

Quindi, nel caso di browser e nodejs (e probabilmente molti altri motori) javascript non è multithread ma lo sono i motori stessi .


Aggiornamento sui web-worker:

La presenza di web-worker giustifica inoltre che javascript possa essere multi-thread, nel senso che qualcuno può creare codice in javascript che verrà eseguito su un thread separato.

Tuttavia: i web-worker non valutano i problemi dei thread tradizionali che possono condividere il contesto di esecuzione. Le regole 2 e 3 sopra si applicano ancora , ma questa volta il codice threaded viene creato dall'utente (js code writer) in javascript.

L'unica cosa da considerare è il numero di thread generati, dal punto di vista dell'efficienza (e non della concorrenza ). Vedi sotto:

Informazioni sulla sicurezza del thread :

L'interfaccia di Worker genera thread a livello di sistema operativo reale e i programmatori consapevoli potrebbero essere preoccupati che la concorrenza possa causare effetti "interessanti" nel tuo codice se non stai attento.

Tuttavia, poiché i web worker hanno controllato attentamente i punti di comunicazione con altri thread, in realtà è molto difficile causare problemi di concorrenza . Non è possibile accedere ai componenti non thread-safe o al DOM. E devi passare dati specifici dentro e fuori un thread attraverso oggetti serializzati. Quindi devi lavorare molto duramente per causare problemi nel tuo codice.


PS

Oltre alla teoria, sii sempre preparato su possibili casi angolari e bug descritti nella risposta accettata


7

JavaScript / ECMAScript è progettato per vivere all'interno di un ambiente host. In altre parole, JavaScript non fa nulla, a meno che l'ambiente host non decida di analizzare ed eseguire un determinato script e di fornire oggetti di ambiente che consentano a JavaScript di essere effettivamente utile (come il DOM nei browser).

Penso che una determinata funzione o blocco di script verrà eseguito riga per riga e ciò è garantito per JavaScript. Tuttavia, forse un ambiente host potrebbe eseguire più script contemporaneamente. Oppure, un ambiente host può sempre fornire un oggetto che fornisce il multi-threading. setTimeoute setIntervalsono esempi, o almeno pseudo-esempi, di un ambiente host che fornisce un modo per fare un po 'di concorrenza (anche se non è esattamente concorrenza).


7

In realtà, una finestra padre può comunicare con finestre o frame secondari o di pari livello con i propri thread di esecuzione in esecuzione.


6

@Bobince sta fornendo una risposta davvero opaca.

Sfogliando la risposta di Már Örlygsson, Javascript è sempre a thread singolo a causa di questo semplice fatto: tutto in Javascript viene eseguito lungo una singola sequenza temporale.

Questa è la definizione rigorosa di un linguaggio di programmazione a thread singolo.


4

No.

Vado contro la folla qui, ma abbi pazienza. Un singolo script JS deve essere efficacemente single thread, ma ciò non significa che non possa essere interpretato diversamente.

Supponiamo che tu abbia il seguente codice ...

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

Questo è scritto con l'aspettativa che entro la fine del ciclo, l'elenco deve avere 10000 voci che sono l'indice al quadrato, ma la VM potrebbe notare che ogni iterazione del ciclo non influisce sull'altra e reinterpretare usando due thread.

Primo thread

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

Secondo thread

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

Sto semplificando qui, perché gli array JS sono più complicati dei blocchi di memoria stupidi, ma se questi due script sono in grado di aggiungere voci all'array in modo thread-safe, allora quando entrambi avranno terminato l'esecuzione avrà lo stesso risultato della versione a thread singolo.

Sebbene non sia a conoscenza di alcuna VM che rilevi un codice parallelizzabile come questo, sembra probabile che possa nascere in futuro per le VM JIT, poiché potrebbe offrire maggiore velocità in alcune situazioni.

Portando ulteriormente questo concetto, è possibile che il codice possa essere annotato per far sapere alla VM cosa convertire in codice multi-thread.

// 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;
}

Dal momento che i Web Worker stanno arrivando a Javascript, è improbabile che questo ... sistema più brutto possa mai esistere, ma penso che sia sicuro dire che Javascript è single-threaded dalla tradizione.


2
La maggior parte delle definizioni linguistiche è progettata per essere efficacemente a thread singolo, tuttavia, e afferma che il multithreading è consentito purché l'effetto sia identico. (ad es. UML)
jevon,

1
Devo concordare con la risposta semplicemente perché l'attuale ECMAScript non prevede (anche se probabilmente penso che lo stesso si possa dire per C) per contesti di esecuzione simultanea di ECMAScript . Quindi, come questa risposta, direi che qualsiasi implementazione che ha thread simultanei in grado di modificare uno stato condiviso, è un'estensione ECMAScript .
user2864740,

3

Bene, Chrome è multiprocesso e penso che ogni processo abbia a che fare con il proprio codice Javascript, ma per quanto ne sa il codice, è "single-thread".

Non esiste alcun supporto in Javascript per il multi-threading, almeno non esplicitamente, quindi non fa differenza.


2

Ho provato l'esempio di @ bobince con una leggera modifica:

<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>

Quindi, quando premi Esegui, chiudi il popup di avviso e fai un "singolo thread", dovresti vedere qualcosa del genere:

click begin
click end
result = 20, should be 20

Ma se provi a eseguirlo in Opera o Firefox stabile su Windows e riduci / ingrandisci la finestra con il popup di avviso sullo schermo, allora ci sarà qualcosa del genere:

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

Non voglio dire che si tratta di "multithreading", ma alcuni frammenti di codice erano stati eseguiti in un momento sbagliato e non me lo aspettavo, e ora ho uno stato corrotto. E meglio sapere di questo comportamento.


-4

Cerca di annidare due funzioni setTimeout l'una nell'altra e si comporteranno in multithread (ovvero, il timer esterno non attende il completamento di quello interno prima di eseguire la sua funzione).


5
Chrome fa questo nel modo giusto, non so dove @James lo vede multithread ...: setTimeout(function(){setTimeout(function(){console.log('i herd you liek async')}, 0); alert('yo dawg!')}, 0)(per la cronaca, yo dawg dovrebbe sempre venire prima, quindi l'output del registro della console)
Tor Valamo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.