Secondo me, questa è una favolosa domanda di intervista - almeno ipotizzando (1) che il candidato abbia una profonda conoscenza del threading e (2) l'intervistatore abbia anche una profonda conoscenza e stia usando la domanda per sondare il candidato. È sempre possibile che l'intervistatore cercasse una risposta specifica e ristretta, ma un intervistatore competente dovrebbe cercare quanto segue:
- Capacità di differenziare i concetti astratti dall'implementazione concreta. Lo lancio principalmente come meta-commento su alcuni dei commenti. No, non ha senso elaborare un unico elenco di parole in questo modo. Tuttavia, il concetto astratto di una pipeline di operazioni, che può estendersi su più macchine con capacità diverse, è importante.
- Nella mia esperienza (circa 30 anni di applicazioni distribuite, multi-processo e multi-thread), la distribuzione del lavoro non è la parte difficile. Raccogliere i risultati e coordinare processi indipendenti sono i punti in cui si verificano la maggior parte dei bug (ancora una volta, nella mia esperienza). Distillando il problema in una catena semplice, l'intervistatore può vedere quanto il candidato pensa bene sul coordinamento. Inoltre, l'intervistatore ha l'opportunità di porre ogni sorta di domande successive, come "OK, e se ogni thread deve inviare la sua parola ad un altro thread per la ricostruzione."
- Il candidato pensa a come il modello di memoria del processore potrebbe influenzare l'implementazione? Se i risultati di una operazione non vengono mai svuotati dalla cache L1, si tratta di un bug anche se non c'è concorrenza apparente.
- Il candidato separa il threading dalla logica dell'applicazione?
Quest'ultimo punto è, a mio parere, il più importante. Di nuovo, in base alla mia esperienza, diventa esponenzialmente più difficile eseguire il debug del codice threadato se il threading è combinato con la logica dell'applicazione (basta guardare tutte le domande Swing su SO per gli esempi). Credo che il miglior codice multi-threaded sia scritto come codice single-threaded autonomo, con handoff definiti chiaramente.
Con questo in mente, il mio approccio sarebbe quello di dare a ogni thread due code: una per l'input, una per l'output. Il thread si blocca durante la lettura della coda di input, prende la prima parola fuori dalla stringa e passa il resto della stringa alla coda di output. Alcune delle caratteristiche di questo approccio:
- Il codice dell'applicazione è responsabile della lettura di una coda, dell'elaborazione di dati e della scrittura della coda. Non importa se è multi-thread o no, o se la coda è una coda in memoria su una macchina o una coda basata su TCP tra macchine che vivono su lati opposti del mondo.
- Poiché il codice dell'applicazione è scritto come se fosse single-thread, è testabile in modo deterministico senza la necessità di un sacco di scaffolding.
- Durante la sua fase di esecuzione, il codice dell'applicazione possiede la stringa in fase di elaborazione. Non deve preoccuparsi della sincronizzazione con i thread in esecuzione simultanea.
Detto questo, ci sono ancora molte aree grigie che un intervistatore competente può sondare:
- "OK, ma stiamo cercando di vedere le tue conoscenze sulle primitive della concorrenza, puoi implementare una coda di blocco?" La tua prima risposta, ovviamente, dovrebbe essere quella di utilizzare una coda di blocco pre-costruita dalla tua piattaforma preferita. Tuttavia, se si capiscono i thread, è possibile creare un'implementazione di coda in meno di una dozzina di righe di codice, utilizzando qualsiasi primitiva di sincronizzazione supportata dalla piattaforma.
- "Che cosa succede se un passaggio nel processo richiede molto tempo?" Dovresti pensare se vuoi una coda di output limitata o illimitata, come potresti gestire gli errori e gli effetti sul throughput complessivo se hai un ritardo.
- Come accodare in modo efficiente la stringa di origine. Non è necessariamente un problema se si ha a che fare con code in memoria, ma potrebbe esserci un problema se ci si sposta tra le macchine. Potresti anche esplorare wrapper di sola lettura sopra un array di byte immutabile sottostante.
Infine, se hai esperienza nella programmazione concorrente, potresti parlare di alcuni framework (ad esempio, Akka per Java / Scala) che già seguono questo modello.