Questo sarebbe un uso appropriato del threading?

3

Dopo aver letto vari articoli, esercitazioni e post di MSDN qui ho trovato un progetto per un servizio di Windows che volevo assicurarmi che la mia strategia di threading fosse corretta e che non porti a problemi di memoria, CPU, database.

Ecco il flusso di base.

MAIN THREAD

  1. Raccogli l'elenco dei servizi da caricare.
  2. Carica ogni servizio.

SERVICE THREAD (avviato dall'evento timer al passaggio 3)

  1. Avvia il servizio
  2. Setup Timer - Setup in modo che non ci siano sovrapposizioni, solo 1 chiamata di elaborazione può essere effettuata.
  3. Metodo di elaborazione
    • Effettua una chiamata al database per ottenere informazioni che sono un elenco di Commands .
    • Raccoglie una raccolta di Handlers .
    • Esiste un Parallel.ForEach() nell'elenco di Commands .
    • Immediatamente fa un normale foreach() su Handlers

THREADS DI COMANDO

  1. Comando fa un% normaleforeach() su Handlers che passa in Command .
  2. Ogni Handler esegue codice che può CRUD il database o raggiungere altri servizi.
    • A causa di Parallel.ForEach() sto girando su 1 nuovo thread per Command .
    • Ci potrebbe essere 1000 +
    • Poiché ogni Command ha più Handlers questi Handlers verrà eseguito sullo stesso thread di Command .
    • Questo serve a prevenire i conflitti di database poiché ogni Handler funzionerà con lo stesso insieme di dati.
    • Si tratta di fare in modo che ogni Command aspetti i propri thread Handler .

WAIT POINTS

  1. Il servizio attende tutti Commands per terminare l'esecuzione prima di eseguire un altro giro di comandi.
  2. Commands aspetta tutti Handlers fino alla fine.
  3. Handlers aspetta che uno finisca prima di passare al successivo.

Performance

  • 3000x FOREACH Console.WriteLine () avg. 160ms
  • 3000x FORALL Console.WriteLine () avg. 275ms
  • 3000x FOREACH DatabaseCallAsync () avg. 1200ms
  • 3000x FORALL DatabaseCallAsync () avg. 600ms

Interessante come il foreach normale sia più veloce con solo WriteLine () ma due volte più lento con la chiamata al database. Continuo a preoccuparmi di competere per le risorse con gli altri servizi.

DOMANDE

  1. Riconosci eventuali problemi immediati con questo design?
  2. Hai qualche suggerimento che lo renderebbe più efficiente con risorse e velocità?
  3. Sto notando un utilizzo estremamente elevato della CPU su tutti i core quando si esegue solo Console.WriteLines() nel punto di esecuzione effettivo, dovrei limitare i thread?
    • Ho notato che quando usavo un normale foreach() aveva ancora un utilizzo della CPU piuttosto alto ma aveva più punti di riposo, ho ancora bisogno di mettere un contatore lì per vedere quante chiamate sono effettivamente fatte per ciascuna per vedere quale è in realtà l'elaborazione di più, ma in questo momento sono solo preoccupato per l'uso elevato.
posta Tony 08.10.2014 - 18:16
fonte

2 risposte

5
  • Non è necessario utilizzare thread dedicati: un thread è un oggetto costoso, quindi è generalmente preferibile utilizzare il pool di thread poiché riutilizzerà i thread per elaborare i elementi di lavoro . L'utilizzo di un thread dedicato è una buona idea se hai bisogno di archiviazione locale dei thread, identità dei thread, priorità dei thread, pianificazione personalizzata (prelievo di lavoro da una pila o coda di priorità piuttosto che una coda), una priorità più alta degli oggetti di lavoro (se avere migliaia di elementi di lavoro in coda, è probabile che un thread ottenga una porzione di tempo prima) o viceversa se si desidera lasciare la coda libera per qualcos'altro, ecc.

  • L'accodamento di un oggetto di lavoro nel pool di thread è ancora un'operazione moderatamente costosa (superiore a centinaia di nanosecondi). Oltre all'algoritmo utilizzato per regolare dinamicamente il numero di thread nel pool funziona meglio quando ogni oggetto di lavoro non è troppo lungo (diciamo meno di un millisecondo). Se i tuoi elementi di lavoro sono più lunghi di questo o se presentano una scarsa scalabilità (database o contesa di I / O), potresti voler limitare il numero massimo di thread nel pool.

  • Alcuni timer accodano i loro callback su ThreadPool e altri sull'interfaccia utente. È necessario verificare se il timer si configura con qualsiasi altro lavoro che si potrebbe accodare, in particolare gli elementi di lavoro a esecuzione prolungata sul ThreadPool e se è importante che le richiamate vengano eseguite prima degli elementi di lavoro in attesa.

  • Async / await sono per lo più necessari quando l'identità del thread è importante, in genere per gli agenti. Ad esempio, l'interfaccia utente in genere può essere manipolata solo dal thread dell'interfaccia utente (si tratta di un meccanismo di affinità del thread che funge da modello di sincronizzazione) e spesso è necessario inviare un'attività a un thread in background dal thread dell'interfaccia utente, quindi proseguire successivamente sull'interfaccia utente ancora. Mentre da una parte i servizi sembrano agenti, dall'altra parte potrebbe essere un errore di progettazione come ho appena spiegato e comunque non sembra che sia necessario inviare nuovamente le continuazioni al servizio.

    Tuttavia le attività sono una bella astrazione da usare e Parallel.Foreach, Task.Run e altri useranno il pool di thread per impostazione predefinita.

Modificata la sezione async / await per tenere in considerazione i commenti sottostanti di Robert Harvey Modificato per menzionare le richiamate dei timer.

    
risposta data 08.10.2014 - 20:01
fonte
0

Il tuo design sembra solido, anche se il punto più lento dell'intera operazione sarà la lettura dal database. Prendi in considerazione l'utilizzo di una chiamata Async , in quanto ciò libererà il processore per eseguire altre azioni in attesa della chiamata di ritorno dal database.

La tua Parallel.ForEach è sicuramente l'opzione migliore, mentre usa il pool di thread (come indicato), separa anche la coda dal pool di thread e ha essenzialmente una coda per processore quindi non c'è attesa in una serratura bloccare mentre chiedono il prossimo compito. Poi, quando finiscono la fila, vanno al prossimo e lo chiedono per alcuni (incorrendo in un piccolo colpo alle prestazioni, ma molto meno che se usassero tutti la stessa coda.)

L'unica domanda che potresti voler pensare è: cosa fa il thread principale mentre i thread di supporto sono in esecuzione? Se non sta facendo nulla, allora si sta solo mangiando il tempo del processore che potrebbe essere speso altrove. Anche se lo hai (il thread principale) dormi fino a quando gli altri thread sono terminati, si riattiva ogni tanto solo per riaddormentare, e se usi qualcosa come un ManualResetEvent, che aspetta fino a quando l'altro thread non è completato per continuare, allora sta occupando inutilmente la memoria. Fai qualcosa, anche se si trasforma in un thread di servizio.

In breve, Parallel.ForEach = Buono!

Utilizza Async e Await per le chiamate al database

Non creare thread non necessari.

    
risposta data 09.10.2014 - 21:26
fonte

Leggi altre domande sui tag