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.