|
|
|
![]() |
|
Strumenti |
![]() |
#1 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
[Threading/C++]Idle Wait
Salve,
stavo provando a reinventare un accrocchio per il threading task-based... quindi creo n threads che dovranno poi eseguire i tasks che gli passo. Però, c'è il problema che i threads ausiliari occupano sempre il 100% del core a loro assegnato, indipendentemente da cosa stanno eseguendo... Per ora ho risolto mettendo un lock se il thread non ha tasks da eseguire, che lo stoppa, però volevo approfondire la differenza tra il thread "main" e i thread ausiliari... Se nel thread principale metto solo un if() su qualche valore eseguito in loop, la CPU rimane quasi vuota... mentre negli altri threads schizza al 100%. è possibile "emulare" questo comportamento negli altri threads? |
![]() |
![]() |
![]() |
#2 |
Senior Member
Iscritto dal: May 2001
Messaggi: 12814
|
Ma il thread è messo in un ciclo while(true) con relativo periodo di sleep?
tipo: Codice:
while(true): # fai qualcosa sleep(1000) # dorme per un secondo |
![]() |
![]() |
![]() |
#3 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Si:
Codice:
boost::mutex sleepMutex; boost::unique_lock<boost::mutex> sleepLock( sleepMutex ); while(working) { if( sleeping ) sleepCondition.wait( sleepLock ); update(); this_thread::yield(); } Infatti anche se rallentato il sistema è fluido... |
![]() |
![]() |
![]() |
#4 | |
Senior Member
Iscritto dal: May 2001
Messaggi: 12814
|
Quote:
Cmq una cosa carina da implementare è un modello che crea thread dinamicamente al bisogno, ovvero solo quando ci sono effettivamente work da effettuare. Oppure un'altra strategia potrebbe essere quella di crearne un po' e lasciarli dormienti... fai un lock per ogni thread (a cui può accedere solo il master) e sbloccarli solo quando ci sono compiti da effettuare (puoi fare una condition su una variabile puntatore a funzione). Dovresti anche tenere traccia di quali sono i thread occupati, poiché il master deve saperlo (ed in caso se li trova in gran parte occupati dovrebbe crearne altri). |
|
![]() |
![]() |
![]() |
#5 |
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
non capisco una cosa... a cosa servono l'if e la yield ?
Sono ridondanti ed inutili se usi il lock.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
![]() |
![]() |
![]() |
#6 | |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quote:
Tuttavia lo stesso identico codice nel Main Thread genera un'occupazione del 3 o 4% massimo, evidentemente ha qualche controllo in più? Il lock serve per spegnere il thread, e l'if è necessario perchè sleepCondition.wait() va chiamato nel thread che deve essere messo ad aspettare, non posso chiamarlo in "sleep()". Se non metto l'if e il lock non è acquisito crasha. @WarDuck: è proprio quello che sto facendo... mi creo n "WorkerThreads", dove n = numero_thread_nativi*2 (per qualche motivo sulla mia CPU genera le massime prestazioni) Questi chiedono dei Tasks ad un Dispatcher che mantiene una queue; se c'è una "Task request miss", cioè non c'è niente da fare, il thread si iberna. Il Dispatcher poi quando riceve un nuovo Task lo userà per svegliare uno dei thread inattivi invece di metterlo in coda, se possibile. I thread vengono creati all'inizio dal dispatcher e sono privati, il loro numero non dovrebbe mai variare... e poi l'intero attrezzo serve PROPRIO ad evitare i locks obbligando relazioni di dipendenza tra i tasks... infatti l'unico lock che c'è nel programma sta in "issue" e "request" perchè altrmenti la queue da i numeri ![]() Tornando IT: mi sembra di aver capito che non è ottimale il mio uso dello sleepLock, come faccio a fare un lock "a cui può accedere solo il master"? |
|
![]() |
![]() |
![]() |
#7 |
Senior Member
Iscritto dal: Feb 2007
Città: Imperia "S.S.28"
Messaggi: 905
|
Mi intrometto se ti interessa esiste una libreria zthread che permette di fare quello che chiedi te... Puo creare un pool di x thread ed hai una lista di task che vengono mandati in esecuzione su quei thread o accodati...
__________________
Dont drink and drive but smoke and fly ![]() Peugeot 206 enfant terrible!!! |
![]() |
![]() |
![]() |
#8 | ||
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Non ho molta pratica di come usare le boost in questo senso, ma ad esempio sotto unix userei una posix_queue e mi toglierei tutte le rogne. Quote:
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
||
![]() |
![]() |
![]() |
#9 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Uhm... per rispondere anche a monelli, sto facendo sta cosa soprattutto per capire il threading, quindi si, molte cose potrebbero non avere alcun senso
![]() Per esempio, che è una "richiesta bloccante"? Per le dipendenze: un Task è un nodo di un'albero di dipendenze; per cui il Dispatcher percorre questo albero e "stacca" le foglie, che sono i Task pronti per essere eseguiti... in questa maniera se si organizza bene la struttura si riesce a sfruttare un numero arbitrario di cores, indirizzando le foglie allo stesso livello (cioè indipendenti) su thread separati. Nota: non è che me lo sono inventato, ho copiato spudoratamente Intel Concurrent Collections ![]() Quindi i lock servono solo a task finito, ma non durante come potrebbe essere in un sistema tradizionale con un thread che fa la fisica uno che fa la grafica etc. E poi ci sono locks e locks... un conto è un lock attivato sempre che blocca tutto un thread aspettando altro; un conto è il lock sulla queue, che ha più una funzione di sicurezza dato che al 99% il programma funge anche senza. Ultima modifica di Tommo : 26-02-2010 alle 16:18. |
![]() |
![]() |
![]() |
#10 |
Senior Member
Iscritto dal: Oct 2007
Città: Padova
Messaggi: 4131
|
[Parzialmente OT]
@Tommo: dato che mi sembra che stiate parlando di usare dei "working threads" ti posto questo articolo presente nel sito della IBM (il linguaggio di rif. è Java, ma nell'articolo la discussione teorica sui thread pool e le "working" queue è valida a prescindere, e non si fa riferimento a particolari librerie dell'SDK per Java [che all'epoca dell'articolo ancora non esistevano, almeno ufficialmente] ma si basa sulle sole funzionalità di multithreading nude e crude incorporate nel linguaggio). - Java theory and practice: Thread pools and work queues Poi, a questa pagina di wikipedia ho visto che ci sono link a zilionate di informazioni. -> http://en.wikipedia.org/wiki/Thread_pool_pattern @Edit: compresa la pagina che ti ho linkato sopra
__________________
As long as you are basically literate in programming, you should be able to express any logical relationship you understand. If you don’t understand a logical relationship, you can use the attempt to program it as a means to learn about it. (Chris Crawford) Ultima modifica di banryu79 : 26-02-2010 alle 17:14. |
![]() |
![]() |
![]() |
#11 | ||
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Quote:
Io una buona implementazione della tua idea la vedrei nel seguente modo: - Utilizzi due code. Una coda di task da fare e una di task fatti. Il thread principale legge i task fatti e mette i task da fare. I thread worker leggono dalla coda dei task da fare e spingono indietro quelli fatti - Il thread principale procede come segue: Codice:
* fa una prima scansione dell'albero : spingi ogni foglia "libera" sullla coda dei task da fare * Mettiti in ascolto sulla coda dei task fatti. Per ogni task che arriva verifica il padre del task. Se il padre ha tutti i figli completati, allora spingi tale nodo sui task da fare Codice:
* mettiti in ascolto sulla coda dei task. Quando arriva un task eseguilo, una volta finito rimettilo sull'altra coda
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
||
![]() |
![]() |
![]() |
#12 |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
La condition non avrebbe bisogno di avere una mutex acquisita ?
Codice:
boost::mutex sleepMutex; boost::unique_lock<boost::mutex> sleepLock( sleepMutex ); while(working) { sleepLock.lock(); if( sleeping ) sleepCondition.wait( sleepLock ); sleepLock.unlock(); update(); this_thread::yield(); } Ultima modifica di cionci : 26-02-2010 alle 20:03. |
![]() |
![]() |
![]() |
#13 |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Io sinceramente farei tutto senza condition...una coda circolare con i thread in attesa, una mutex per ogni elemento della coda. Quando un thread è disponibile acquisisce un elemento della coda, la relativa mutex e scrive i risultati in un'apposita struttura (ad esempio allocando una opportuna struttura dati).
Il thread principale si occupa di recuperare i risultati e poi fa una signal sulla mutex. La struttura della coda può essere quella classica, con semaforo pieno e semaforo vuoto. |
![]() |
![]() |
![]() |
#14 |
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Grazie per le idee, mi sa che qua ci sta un bel refactoring
![]() La coda dei task finiti in realtà non mi è sembrata troppo utile, perchè richiede un lock in più quando ci si va a scrivere, e poi un altro thread che volesse trovarci dentro un task a cui è interessato dovrebbe leggersela tutta. Invece io ho messo uno stato interno di Task (completed, etc), un Listener, e un Garbage Collector per i Tasks: cioè se si assegna un listener ad un task, quando termina questo aggiunge un evento "listenedTaskCompleted" su una queue di Dispatcher, quindi dispatcher provvede a chiamare il Listener quando ci si trova sul main thread. Quindi (se il thread è "autoDeletable"), percorre l'albero a partire dal Task completato e distrugge quello e tutti i parents diventati inutili col completamento del Task corrente (es: erano aspettati solo da lui). Comunque il problema principale è che la documentazione di Boost fa un pò pena, è difficile capire come fungono mutex eccetera. @cionci: mi sembra che lock e unlock li gestisce wait, nel senso che quando ci entra chiama unlock() + lock() e quando esce chiama lock() + unlock() se possibile. |
![]() |
![]() |
![]() |
#15 | |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
Solitamente la condition si chiama per attendere che si verifichi una determinata condizione. Questa condizione viene testata tramite variabili condivise, è chiaro che queste variabili condivise debbano essere gestite in mutua esclusione. Inoltre se guardi la documentazione vedrai che genera un'eccezione se il lock non è acquisito. Se acquisisse il lock dentro la wait la mutua esclusione sulla condizione non sarebbe garantita. Ci potrebbero essere tra l'altro delle corse critiche. Ultima modifica di cionci : 27-02-2010 alle 10:59. |
|
![]() |
![]() |
![]() |
#16 | ||
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
![]() Quote:
Puoi sistemare facilmente... riempiendo di lock l'albero ( o anche uno solo che blocchi tutto ). Peggio ancora. Se la callback viene eseguita all'atto della conclusione del task, per come l'hai descritto verra' eseguita dal thread stesso. Il che vuol dire che tutti i processori della tua macchina dovrano costantemente sincronizzare la loro cache devastando le prestazioni. Idealmente vuoi che le informazioni condivise tra i vari thread siano il minimo possibile. Se riesci a far stare l'albero sulla cache di un solo processore è molto meglio, tanto non ti serve per eseguire i singoli task. E l'implementazione, ripeto, è veramente semplcie.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
||
![]() |
![]() |
![]() |
#17 |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Non avevo letto molto bene la struttura che vuoi realizzare. Comunque la concorrenza secondo me può essere gestita sui risultati, non sulla struttura dei thread. I thread dovrebbero essere gestiti separatamente.
Dato un albero di dipendenza il dispatcher si occupa solo di allocare il o i thread che dipendono da tutti gli altri. C'è un canale di comunicazione fra i thread. Un thread che dipende da un altro si registra sul canale inviando i dati da elaborare e si mette in attesa dei risultati. Quando l'elaborazione di un thread finisce questo invia i risultati sul canale di comunicazione e si mette in attesa di ricevere nuovi dati su cui lavorare. |
![]() |
![]() |
![]() |
#18 | |||
Senior Member
Iscritto dal: Feb 2006
Messaggi: 1304
|
Quote:
quello che intendevo con "poco utile" è che con una semplice completed queue hai che il main thread si deve prendere la briga di verificare da sè lo stato di completamento, quindi andarsi a cercare il task in oggetto tra i completi e toglierlo. Nè comodo nè efficiente ![]() Quote:
Il lock serve solo quando il WorkerThread notifica il Dispatcher che un Task Listened (non lo sono tutti, anzi, dovrebbero essere uno o due) è completato. Questo fa si che il Task sia considerato automaticamente NON deletable, ma viene cancellato solo dal main thread alla fine della chiamata al listener se permane in stato "autoDeletable". Però non c'entra niente, ma il problema dell'albero in effetti esiste... dato che tutti i Task eccetto quelli listened vengono eliminati sul thread di appartenenza... mi sa che in effetti a sto punto va unificata la cosa con una completed queue ![]() Quote:
nel mio design si allocano n thread (con n sconosciuto all'utente, e con i thread classe privata di Dispatcher) e viene esposta solo l'interfaccia "ad albero" dei Task. La dipendenza è resa semplicemente dalle relazioni tra questi ultimi, e i thread sono faccenda del dispatcher... Un task dovrebbe essere completamente data parallel, cioè deve eseguirsi dall'inizio alla fine senza alcuna richiesta verso threads di livello uguale o superiore al suo, per definizione ![]() Comunque, non pensavo di doverlo mai chiedere in questi termini ma avete un buon testo di riferimento da studiare? ![]() Il threading mi sembra proprio difficile da afferrare per prove... PS: preferibilmente roba gratis ![]() |
|||
![]() |
![]() |
![]() |
#19 | |
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
![]() Ad esempio puoi implementare con due stati: - registrazione richieste sul canale - elaborazione e invio risultati sul canale I due stati possono anche non necessariamente essere lo stesso thread ![]() Il canale può anche essere il dispatcher stesso. Alloco il thread A, nel costruttore si registra sul canale per un thread di tipo B e due thread di tipo C. Termina il costruttore. Il canale vedendo le richieste alloca i thread richiesti. Il thread di tipo B richiede l'esecuzione di un thread del tipo D. Il canale alloca un thread di tipo D. Il thread di tipo D non registra alcuna richiesta, quindi i requisiti sono soddisfatti e il canale "invia i risultati" (fra virgolette perché di fatto mette in esecuzione il thread). Alla fine di D, D invia i risultati sul canale, il canale verifica che i requisiti del thread B sono soddisfatti...invia i risultati ed esegue il thread B. L'unico che cicla è il thread del canale. Volendo si potrebbe lavorare anche su questo aspetto. |
|
![]() |
![]() |
![]() |
#20 | |
Senior Member
Iscritto dal: Dec 2005
Città: Istanbul
Messaggi: 1817
|
Quote:
Se ci dai un esempio di come dovrebbero essere le api si puo' provare una implementazione rapida, cosi' possiamo confrontare le varie idee.
__________________
One of the conclusions that we reached was that the "object" need not be a primitive notion in a programming language; one can build objects and their behaviour from little more than assignable value cells and good old lambda expressions. —Guy Steele |
|
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 23:10.