Differenza tra consumatore / produttore e osservatore / osservabile


15

Sto lavorando alla progettazione di un'applicazione composta da tre parti:

  • un singolo thread che controlla il verificarsi di determinati eventi (creazione di file, richieste esterne ecc.)
  • N thread di lavoro che rispondono a questi eventi elaborandoli (ogni lavoratore elabora e consuma un singolo evento e l'elaborazione può richiedere un tempo variabile)
  • un controller che gestisce quei thread ed esegue la gestione degli errori (riavvio dei thread, registrazione dei risultati)

Anche se questo è piuttosto semplice e non difficile da implementare, mi chiedo quale sarebbe il modo "giusto" per farlo (in questo caso concreto in Java, ma sono apprezzate anche risposte di astrazione più elevate). Mi vengono in mente due strategie:

  • Osservatore / osservabile: il thread di osservazione viene osservato dal controller. In caso si verifichi un evento, il controller viene quindi avvisato e può assegnare la nuova attività a un thread gratuito da un pool di thread nella cache riutilizzabile (oppure attendere e memorizzare nella cache le attività nella coda FIFO se tutti i thread sono attualmente occupati). I thread di lavoro implementano Callable e restituiscono con successo il risultato (o un valore booleano) oppure restituiscono un errore, nel qual caso il controller può decidere cosa fare (a seconda della natura dell'errore che è successo).

  • Produttore / consumatore : il thread di sorveglianza condivide BlockingQueue con il controller (coda eventi) e il controller ne condivide due con tutti i lavoratori (coda attività e coda risultati). In caso di un evento, il thread di sorveglianza inserisce un oggetto task nella coda degli eventi. Il controller prende nuove attività dalla coda degli eventi, le esamina e le inserisce nella coda delle attività. Ogni lavoratore attende nuove attività e le prende / le consuma dalla coda delle attività (primo arrivato, primo servito, gestito dalla coda stessa), rimettendo i risultati o gli errori nella coda dei risultati. Infine, il controller può recuperare i risultati dalla coda dei risultati e adottare le misure appropriate in caso di errori.

I risultati finali di entrambi gli approcci sono simili, ma ognuno presenta lievi differenze:

Con Observers, il controllo dei thread è diretto e ogni attività è attribuita a un nuovo lavoratore generato specifico. Il sovraccarico per la creazione di thread potrebbe essere maggiore, ma non molto grazie al pool di thread nella cache. D'altra parte, il modello Observer è ridotto a un singolo Observer anziché a multiplo, il che non è esattamente quello per cui è stato progettato.

La strategia della coda sembra essere più facile da estendere, ad esempio l'aggiunta di più produttori invece di uno è semplice e non richiede alcun cambiamento. Il rovescio della medaglia è che tutti i thread funzionerebbero indefinitamente, anche quando non eseguono alcun lavoro, e la gestione di errori / risultati non sembra elegante come nella prima soluzione.

Quale sarebbe l'approccio più appropriato in questa situazione e perché? Ho trovato difficile trovare risposte a questa domanda online, perché la maggior parte degli esempi tratta solo casi chiari, come l'aggiornamento di molte finestre con un nuovo valore nel caso Observer o l'elaborazione con più consumatori e produttori. Ogni input è molto apprezzato.

Risposte:


10

Sei abbastanza vicino a rispondere alla tua domanda. :)

Nel modello Osservabile / Osservatore (notare il capovolgimento), ci sono tre cose da tenere a mente:

  1. In generale, la notifica della modifica, ovvero "payload", è osservabile.
  2. L'osservabile esiste .
  3. Gli osservatori devono essere conosciuti dall'osservabile esistente (altrimenti non hanno nulla su cui osservare).

Combinando questi punti, ciò che è implicito è che l'osservabile sa quali sono i suoi componenti a valle, cioè gli osservatori. Il flusso di dati è intrinsecamente guidato dall'osservabile: gli osservatori semplicemente "vivono e muoiono" per ciò su cui stanno osservando.

Nel modello Produttore / Consumatore si ottiene un'interazione molto diversa:

  1. In generale, il carico utile esiste indipendentemente dal produttore responsabile della sua produzione.
  2. I produttori non sanno come o quando i consumatori sono attivi.
  3. I consumatori non devono necessariamente conoscere il produttore del payload.

Il flusso di dati è ora completamente interrotto tra un produttore e un consumatore: tutto ciò che il produttore sa è che ha un output e tutto il consumatore sa che ha un input. È importante sottolineare che ciò significa che produttori e consumatori possono esistere interamente senza la presenza dell'altro.

Un'altra differenza non così sottile è che più osservatori sullo stesso osservabile di solito ottengono lo stesso carico utile (a meno che non vi sia un'implementazione non convenzionale), mentre non possono esserlo più consumatori dello stesso produttore. Ciò dipende se l'intermediario è un approccio simile a una coda o a un argomento. Il primo trasmette un messaggio diverso per ciascun consumatore, mentre il secondo assicura (o tenta di) che tutti i consumatori elaborino in base al messaggio.

Per adattarli alla tua applicazione:

  • Nel modello Osservabile / Osservatore, ogni volta che il thread di osservazione viene inizializzato, deve sapere come informare il controller. In qualità di osservatore, è probabile che il controller sia in attesa di una notifica dal thread di monitoraggio prima che consenta ai thread di gestire la modifica.
  • Nel modello Produttore / Consumatore, il tuo thread di monitoraggio deve solo conoscere la presenza della coda degli eventi e interagire esclusivamente con quello. In quanto consumatore, il controller esegue quindi il polling della coda degli eventi e, una volta ottenuto un nuovo payload, consente ai thread di gestirlo.

Pertanto, per rispondere direttamente alla tua domanda: se vuoi mantenere un certo livello di separazione tra il tuo thread di osservazione e il tuo controller in modo da poterli operare in modo indipendente, dovresti tendere verso il modello Produttore / Consumatore.


2
Grazie per la tua risposta dettagliata. Sfortunatamente non posso votarlo a causa della mancanza di reputazione, quindi l'ho contrassegnato come soluzione. L'indipendenza temporale tra le due parti che hai citato è qualcosa di positivo a cui non ho pensato finora. Le code potrebbero gestire brevi raffiche di molti eventi con lunghe pause tra molto meglio dell'azione diretta dopo che gli eventi sono stati osservati (se il numero massimo di thread è fisso e relativamente basso). Il conteggio dei thread potrebbe anche essere aumentato / ridotto in modo dinamico a seconda del conteggio degli elementi della coda corrente.
user183536

@ user183536 Nessun problema, felice di aiutarti! :)
hjk
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.