Per aumentare il parallelismo, vogliamo esaminare diverse cose: (i) suddividere il lavoro e (ii) problemi di latenza.
Per aumentare le prestazioni (supponendo che ora abbiamo il parallelismo) vogliamo (iii) esaminare come vengono utilizzate le cache e (iv) evitare inutili sovraccarichi del sistema operativo associati al cambio di contesto (dall'avere troppi thread legati alla CPU, come non solo il cambio di contesto ha il suo overhead, ma tende anche a raffreddare le cache).
Suddividiamo il lavoro (i) in modo che possano essere applicate più CPU e identifichiamo operazioni a latenza elevata in modo da poter utilizzare un numero inferiore di thread complessivi.
Nei passaggi sequenziali, può esistere una significativa latenza di rete tra la richiesta dell'URL e la ricezione del documento a quell'URL.
Se dedichiamo il / i lavoratore / i a fare cose che implicano latenza, avremo spesso dei lavoratori bloccati in attesa di ricevere risposte dalla rete. A causa di ciò, avremo bisogno di avere molti lavoratori e contiamo sulla lingua e sull'ampiezza efficienza del sistema operativo delle implementazioni di threading per scala.
Questo non vuol dire che questo non possa funzionare, ma un'alternativa che utilizza oggetti semplici invece di thread probabilmente scalerebbe meglio.
Il punto principale che farei è che sembra che mentre il tuo passo (1) ha latenza, passi (2) e amp; (3) non sembrano avere latenza, il che significa che sono probabilmente entrambe le operazioni associate alla CPU.
Quindi, probabilmente avrei due gruppi di lavoratori e una coda di fronte a ciascuno. La prima coda dovrebbe contenere gli URL da visitare e avere un gruppo di lavoro come un semplice thread in background (ad esempio forse un solo worker) per attivare essenzialmente le richieste di rete in modo asincrono. Una cosa che questo operatore potrebbe fare è imporre qualsiasi limitazione necessaria (ad esempio, il recupero totale degli URL in sospeso o gli URL allo stesso server o monitorare la coda di output per essere troppo grande (il che significa che la seconda fase è in ritardo)).
La seconda coda sarà dedicata ai risultati di rete ricevuti e gli operatori manterranno questi risultati man mano che entrano in (2) & (3) insieme. Potresti volere tanti di questi lavoratori quanto tu hai le CPU; più non aiuterà molto e potrebbe anche ferire.
In questo approccio, utilizziamo oggetti più piccoli (rispetto ai thread) per ridimensionarli e mantenere il numero di thread totali più vicino al numero di CPU.
Quindi, non avrei gruppi separati di lavoratori per (2) e (3) connessi con una coda in mezzo, e invece suggerirei un solo gruppo di lavoro, dove questi lavoratori elaborano sequenzialmente (2) e amp; (3). Non vedo alcun punto per separare 2 & 3 (a meno che non ci sia un enorme vantaggio nella cache per l'esecuzione di molti 2 di fila e poi di molti 3 di fila, anche se questo non salta fuori come probabile per quello che stai descrivendo).
Se hai separato 2 & 3, dovresti capire quanti di loro avere, e qui corri il rischio di picchiare le cache se ti capita di passare troppo al cambio di contesto.