2015-05-15 12 views
5

Diciamo, per esempio, che stai scrivendo un programma che aspetta un messaggio in coda, lo gestisce, e poi aspetta il prossimo messaggio, e questo va avanti all'infinito. In un linguaggio come C o Java sarebbe simile a questa:Ci sono perdite di memoria con chiamata javascript una funzione in un callback ricorsivo?

void processMessage() { 
    while (true) { 
     // waitForMessage blocks until the next message is received 
     msg = waitForMessage(); 
     // handle msg here 
    } 
} 

in javascript (sto usando node.js, btw), poiché sono stati utilizzati i callback, normalmente si presenta così:

function processMessage() { 
    waitForMessage(function(msg) { 
     // handle msg or error here 
     processMessage(); 
    }); 
} 

La mia preoccupazione è che in pratica si ha una catena di callback che richiama in modo ricorsivo la funzione originale e il sovraccarico di ciò potrebbe lentamente consumare memoria. Immagino che questo non sia effettivamente un problema dato che forse i callback javascript esistono sul proprio stack in modo indipendente e non vengono inseriti nello stack delle funzioni originali? Qualcuno spiega callback e scoping javascript e mi assicura che il codice javascript non esaurirà la memoria quando viene eseguito per un periodo arbitrariamente lungo durante la ricezione di un numero arbitrariamente grande di messaggi

+2

Questo non realmente * * ricorsiva, poiché ogni callback viene richiamato su una pila fresca quando l'evento asincrono attivazione avviene. In altre parole, la prima chiamata a 'processMessage()' sarà completata a lungo quando il prossimo avviene in risposta all'evento. – Pointy

+0

si sarà in grado di vedere questo in ispettore chrome se si imposta una situazione in cui questo dovrebbe essere eseguito più volte. La mia ipotesi è che non ci dovrebbe essere un problema finché si completano i processi su ogni callback – BillPull

risposta

2

Le funzioni non sono allocate nello stack . La funzione di callback sarà garbage collection dopo che è stata utilizzata a meno che un riferimento venga mantenuto per qualche motivo. Il tuo codice dovrebbe andare bene!

+1

Le chiamate di funzione vengono messe in pila come un mezzo per tenere traccia di dove dovrebbe andare l'esecuzione quando la funzione ritorna. Gli oggetti scope della funzione non vengono messi nello stack perché il loro ciclo di vita può essere diverso dall'esecuzione della funzione stessa, quindi sono raccolti come altri oggetti in Javascript. – jfriend00

4

No, le chiamate di funzione ricorsive non causano perdite di memoria in Javascript.

L'unica memoria utilizzata da una chiamata di funzione è un po 'di spazio di stack (quindi l'interprete sa dove andare quando ritorna la funzione) e qualsiasi memoria viene utilizzata dall'oggetto scope della funzione (ad esempio le variabili locali). Quella memoria dello stack viene completata restituita al sistema quando ritorna la chiamata alla funzione. Non perde.

In JavaScript, con callback asincroni, la funzione di inizializzazione è già stata restituita e quindi lo stack è stato cancellato molto prima che il callback asincrono sia stato chiamato in modo che non ci sia accumulo dello stack.

Ci sarà un oggetto scope della funzione in memoria fino a quando il callback non viene eseguito e ciò è importante ed è necessario per consentire al callback inline di accedere alle variabili dichiarate nell'ambito genitore. Non appena il callback è terminato (non più raggiungibile), tale oggetto sarà raccolto. A meno che non si stia facendo qualcosa di insolito come stringhe giganti allocate o buffer in tale ambito temporaneo, l'utilizzo della memoria di tale ambito non dovrebbe costituire un problema.

Come per il recupero di molti messaggi da una chiamata di funzione iniziale e chiamate ripetute della stessa richiamata, tenere presente che la funzione genitore viene eseguita una sola volta e viene assegnato un solo oggetto di ambito, indipendentemente dal numero di richiamate richiamate quindi non c'è accumulo di memoria per ogni volta che viene richiamato il callback. Il callback stesso otterrà un nuovo ambito di funzione ogni volta che viene chiamato, ma poiché la richiamata stessa non attiva alcuna chiamata asincrona, quell'oggetto dell'ambito sarà temporaneo e sarà idoneo per la garbage collection non appena viene eseguita la richiamata con il suo lavoro e ritorni.

Se si concatenano/eseguono operazioni asincrone l'una nell'altra, gli oggetti di ambito aggiuntivi verranno conservati per la durata dell'operazione asincrona, ma questo è il modo in cui funziona JavaScript ed è ciò che offre le funzionalità di accesso all'ambito principale. In pratica, non è generalmente dimostrato che si tratti di un problema di memoria. Gli oggetti di ambito sono oggetti relativamente compatti (uno è creato per quasi tutte le chiamate di funzioni), come ho detto sopra, purché non si inseriscano buffer giganti o stringhe/array giganti in un ambito persistente, l'utilizzo della memoria di solito non è rilevante .


Anche tenere a mente che quando si chiama processMessage() ancora una volta all'interno della callback asincrono che non è il tipo di ricorsione che si potrebbe pensare in generale perché la precedente chiamata di funzione per processMessage() è già tornato e lo stack ha completamente slegato prima che l'evento asincrono abbia attivato il callback. Quindi, non ci sono accumuli di stack in questo caso. Questo perché le operazioni asincrone in Javascript passano tutte attraverso una coda di eventi. Quando un'operazione asincrona è pronta per attivare un'azione, inserisce un evento nella coda degli eventi di Javascript. Questo evento viene elaborato solo quando il thread corrente dell'operazione JS è terminato e completamente slegato. Solo allora l'interprete JS guarda nella coda degli eventi per vedere se c'è qualcos'altro da fare. Come tale, lo stack viene sempre completamente svolto prima dell'avvio della successiva operazione asincrona.

Per maggiori informazioni su come funziona e un certo numero di articoli di riferimento sulla coda degli eventi JS (che funziona allo stesso modo in node.js che lo fa nel browser), si veda questo articolo:

How does JavaScript handle AJAX responses in the background?

Questo è uno dei motivi per cui Joyent chiama node.js un "un modello I/O non bloccato e comandato da eventi" proprio sul node.js home page.

+0

Per prima cosa non c'è perdita di memoria, quindi dire "L'unica memoria utilizzata da una chiamata di funzione è un po 'di spazio nello stack ...". Essere una piccola quantità di memoria non rende meno una perdita di memoria. Sono abbastanza sicuro che se si richiama una funzione in modo ricorsivo, alla fine si otterrà un overflow di memoria. – icenac

+0

function a() {var b = [100]; for (var i = 0; i <100; i ++) {b [i] = i + "" + i + "" + i;} console.log ('a'); setTimeout (a, 10);}; Qui, prova questo nella tua console e vedi come l'utilizzo della memoria sta aumentando nel tempo. – icenac

+0

@icenac - Lo spazio dello stack non è una memoria ** perdita **, è l'utilizzo della memoria. È solo la memoria utilizzata fino al ritorno della funzione. Chiamando una funzione sincrona all'infinito ricorsivamente si verificherà un eccesso di stack abbastanza rapidamente. Questo è un problema completamente diverso da una perdita di memoria. Qualcosa da evitare, ma una diversa classe di problemi rispetto alle perdite di memoria. – jfriend00

Problemi correlati