Risposta breve: utilizzare pool di thread distinti di dimensioni fisse per operazioni intensive della CPU e per I/O. Oltre alle dimensioni del pool, l'ulteriore regolazione del numero di thread attivi verrà eseguita dal buffer limitato (Producer/Consumer) che sincronizza il computer e le fasi IO del flusso di lavoro.
Per problemi di calcolo e di dati in cui i colli di bottiglia sono una destinazione mobile tra diverse risorse (ad esempio CPU vs IO), può essere utile fare una chiara distinzione tra un thread e un thread, in particolare, come prima approssimazione:
- un filo che viene creato per utilizzare più cicli di CPU ("filo CPU")
- un filo che viene creato per gestire un'operazione asincrona IO ("filo IO")
Più in generale, i thread dovrebbero essere separati da il tipo di risorse di cui hanno bisogno. Lo scopo dovrebbe essere quello di garantire che un singolo thread non utilizzi più di una risorsa (ad esempio evitando di passare dalla lettura dei dati all'elaborazione dei dati nello stesso thread). Quando un battistrada utilizza più di una risorsa, dovrebbe essere diviso e i due thread risultanti dovrebbero essere sincronizzati attraverso un buffer limitato.
In genere, per saturare le pipeline di istruzioni di tutti i core disponibili sul sistema dovrebbero esserci esattamente i thread CPU necessari. Per garantire ciò, è sufficiente disporre di un "pool di thread della CPU" con esattamente tutti i thread dedicati esclusivamente al lavoro di calcolo. Sarebbe boost::
o std::thread::hardware_concurrency()
se ci si può fidare di questo. Quando l'applicazione ha bisogno di meno, ci saranno semplicemente thread inutilizzati nel pool di thread della CPU. Quando ha bisogno di più, il lavoro è in coda. Invece di un "pool di thread della CPU", è possibile utilizzare C++ 11 std::async
ma è necessario implementare un meccanismo di limitazione del thread con la selezione degli strumenti di sincronizzazione (ad esempio un semaforo di conteggio).
Oltre al "pool di thread della CPU", può esistere un altro pool di thread (o diversi altri pool di thread) dedicato alle operazioni di I/O asincrone. Nel tuo caso, sembra che la contesa sulle risorse IO sia potenzialmente una preoccupazione. Se questo è il caso (ad esempio un disco rigido locale) il numero massimo di thread deve essere controllato attentamente (ad esempio al massimo 2 thread di lettura e 2 di scrittura su un disco rigido locale). Questo è concettualmente lo stesso dei thread della CPU e si dovrebbe avere un pool di thread di dimensioni fisse per la lettura e un altro per la scrittura. Sfortunatamente, probabilmente non ci sarà alcuna buona primitiva disponibile per decidere sulla dimensione di questi pool di thread (la misurazione potrebbe essere semplice se i tuoi pattern IO sono molto regolari). Se il conflitto di risorse non è un problema (ad esempio, NAS o richieste HTTP piccole), allora boost::asio
o C++ 11 std::async
sarebbe probabilmente un'opzione migliore di un pool di thread; nel qual caso, la limitazione del filo può essere interamente lasciata ai buffer limitati.
fonte
2015-04-14 23:57:26
Nel punto in cui ci si trova a creare un numero elevato di thread per nascondere la latenza di I/O, è ora di esaminare i pool di thread con IOCP (o epoll, in base al sistema operativo). – Sneftel
Nota che boost :: asio fornisce un livello di astrazione per queste cose – Sneftel
È l'opposto. I nostri file sono abbastanza grandi che un singolo thread può saturare l'IO se non stiamo facendo un'elaborazione intensiva come leggiamo. Poiché l'elaborazione diventa più complicata, le cose possono diventare vincolate alla CPU e quindi beneficiano di un altro thread (o più). In pratica, abbiamo un algoritmo di elaborazione del segnale che ha veramente bisogno dei thread aggiuntivi, ma quando non viene utilizzato, i thread aggiuntivi ci costano molto tempo perché causano (a volte) l'accesso al file in modo errato. –