2015-04-09 10 views
7

Abbiamo un programma C++ che, a seconda del modo in cui l'utente lo configura, potrebbe essere legato alla CPU o vincolato all'IO. Allo scopo di un accoppiamento lento con la configurazione del programma, vorrei che il mio pool di thread si realizzasse automaticamente quando il programma trarrebbe beneficio da più thread (cioè vincolati dalla CPU). Sarebbe bello se si rendesse conto di quando era I/O vincolato e ridotto il numero di lavoratori, ma sarebbe solo un bonus (cioè sarei felice con qualcosa che cresce automaticamente senza il ritiro automatico).Come si aggiungono automaticamente i thread a un pool in base alle esigenze di calcolo del programma?

Usiamo Boost quindi se c'è qualcosa che potrebbe aiutarci possiamo usarlo. Mi rendo conto che qualsiasi soluzione sarebbe probabilmente specifica per la piattaforma, quindi siamo principalmente interessati a Windows e Linux, con un interesse terziario in OS X o in qualsiasi altro * nix.

+1

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

+0

Nota che boost :: asio fornisce un livello di astrazione per queste cose – Sneftel

+0

È 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. –

risposta

4

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.

+0

Ciao Vieni, grazie per la risposta. Non sono sicuro di come applicare la risposta, ma ciò potrebbe essere dovuto al fatto che non ho spiegato abbastanza chiaramente il mio caso d'uso. Il problema con un pool di thread di dimensioni fisse è che ho un numero fisso di attività (migliaia) che verranno accodate e il lavoro del pool è di eseguirle il più rapidamente possibile. Quando le opzioni sono tali che i thread saranno vincolati all'I/O, è meglio se ho solo 1 o 2 thread, perché altrimenti le attività non funzioneranno sul disco, il che aumenta i costi di ricerca e buffering. Se sono legati alla CPU, traggo vantaggio da più thread. –

+0

Quindi stavo pensando a qualcosa di più sulla falsariga della mia classe di dispatcher dell'attività che inizia con un thread, quindi monitorando l'utilizzo della CPU del mio processo rispetto all'utilizzo della CPU del sistema (diviso per il numero di core fisici). Quando il dispatcher rileva che il mio processo utilizza quasi l'intero core, aggiungerà un altro thread. Se inizia a utilizzare due interi core, aggiunge un altro thread. E così via. –

+0

@MattChambers per semplicità Suppongo che tu abbia 8 core e che la lettura ottimale sia su 2 thread. Creare un "pool di thread della CPU" con 8 thread e un "pool di thread di lettura" con 2 thread, con un buffer limitato in mezzo. Se IO è il collo di bottiglia, entrambi i thread letti saranno completamente utilizzati, il buffer limitato rimarrà per lo più vuoto e alcuni dei thread della CPU saranno inattivi. Se la CPU è il collo di bottiglia, tutti i thread della CPU saranno completamente utilizzati, il buffer limitato rimarrà per lo più pieno ei thread letti saranno parzialmente inattivi. Si autoregola e ottimizza sempre attorno al collo di bottiglia. –