Sto lavorando alla progettazione di un'applicazione composta da tre parti:
- un singolo thread che osserva il verificarsi di determinati eventi (creazione di file, richieste esterne ecc.)
- N thread di lavoro che rispondono a questi eventi elaborandoli (ciascun worker elabora e consuma un singolo evento e l'elaborazione può richiedere tempo variabile)
- un controller che gestisce questi thread e gestisce gli errori (riavvio dei thread, registrazione dei risultati)
Anche se questo è abbastanza semplice e non è difficile da implementare, mi chiedo quale sarebbe il modo "giusto" per farlo (in questo caso concreto in Java, ma sono anche apprezzate le risposte più astratte). Vengono in mente due strategie:
-
Observer / Observable: il thread di osservazione viene osservato dal controller. In caso di un evento, il controllore viene quindi informato e può assegnare la nuova attività a un thread libero da un pool di thread memorizzato nella cache (oppure attendere e memorizzare nella cache le attività nella coda FIFO se tutti i thread sono attualmente occupati). I thread worker implementano Callable e restituiscono correttamente con il risultato (o un valore booleano) oppure restituiscono con un errore, nel qual caso il controller può decidere cosa fare (in base alla natura dell'errore che si è verificato).
-
Producer / Consumer : il thread di visualizzazione condivide un BlockingQueue con il controller (coda evento) e il controller ne condivide due con tutti i worker (coda delle attività e coda dei risultati). In caso di un evento, il thread di sorveglianza inserisce un oggetto task nella coda degli eventi. Il controllore acquisisce nuove attività dalla coda degli eventi, le rivede e le inserisce nella coda delle attività. Ogni lavoratore attende nuove attività e le prende / consuma dalla coda delle attività (primo arrivato, primo servito, gestito dalla coda stessa), riportando i risultati o gli errori nella coda dei risultati. Infine, il controller può recuperare i risultati dalla coda dei risultati e prendere le misure necessarie in caso di errori.
I risultati finali di entrambi gli approcci sono simili, ma ognuno presenta lievi differenze:
Con gli osservatori, il controllo dei thread è diretto e ogni attività è attribuita a un nuovo lavoratore spawn. L'overhead per la creazione di thread potrebbe essere più elevato, ma non molto grazie al pool di thread memorizzato nella cache. D'altra parte, il pattern Observer è ridotto a un singolo Observer invece che a più, 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 alcuna modifica. Il rovescio della medaglia è che tutti i thread sarebbero eseguiti indefinitamente, anche quando non funzionano affatto, e la gestione degli errori / dei 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. Qualsiasi input è molto apprezzato.