|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
[C++] QT threads - un'idea migliore ?
Avrei bisogno di qualche consiglio su un programma che sto facendo (C++ - Linux Ubuntu).
In pratica devo creare un record/playback client da inserire nel programma mumble che già usiamo per i nostri software di comunicazione. In fase "record" il client deve registare l'audio e questo l'ho già fatto usando l'ottima libreria free "libsndfile". In fase "playback" ho qualche problemino ... devo sincronizzare un thread "transport" ed uno "playback" in modo che quando quello "transport" riceve un comando (via tcp) tipo play, stop o set-position, quello "playback" esegua la giusta operazione. Devo usare i threads di QT (QThread) perchè mumble li usa quindi mi ritrovo con 2 threads che hanno un metodo run() con un ciclo infinito. Il thread "transport" controlla periodicamente se c'è un comando da eseguire e, se si, informa il thread "playback". Non mi piace la soluzione che ho trovato ma è l'unica ... un QT mutex per ogni comando: es: se arriva un comando stop, il thread "transport" locka il mutex stop e il thread "playback", quando prova a lockarlo, si ferma in attesa che venga sbloccato. Non mi piace perchè ogni 1/4 di secondo i threads devono bloccare/sbloccare un mutex per ogni comando per non parlare poi del rischio deadlock visto il numero di mutex. Qualcuno ha idee migliori ?
__________________
Nintendo WIII 4d Turbo Intercooler - Sestium X 666 99,312 GHz - 6.984 Ram Σ(9999) MHz - HDD SATA 97e^(10) bytes 93³ rpm - ATI biberon X900z ∞Mb - Win Eight SP (1 > yours) 16 Valve |
|
|
|
|
|
#2 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Difficile da dire non conoscendo nei dettagli l'architettura. Ad esempio potresti usare una coda circolare in modo da fare il playback dei comandi solo tutti insieme (nota che inserimento in coda e prelievo lavorano su semafori diversi).
Serve comunque una mutex per l'inserimento perché ci possono essere più thread che tentano di incrementare il contatore. C'è un solo thread che preleva, quindi di fatto non c'è bisogno di alcuna mutex ed il thread che preleva, se non ci sono dati da prelevare, può tornare a fare altro. Di fatto il thread che preleva non ha alcuna attesa. Quelli che scrivono attendono solo di prendere possesso della mutex in scrittura sul contatore oppure attendono in caso di coda piena. |
|
|
|
|
|
#3 | |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Quote:
Che ci sia bisogno del mutex lo so ma non ho capito come fare per usarne solo uno ... |
|
|
|
|
|
|
#4 |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Forse ho capito ...
Potrei lockare una struttura con una variabile che indica l'azione da eseguire e una che indica il valore (per set-position, per esempio). Se è "lockata per scrivere" dal thread transport, la leggo dal thread "playback" per sapere cosa deve fare. Non so se è quello che intendi ma grazie dell'idea |
|
|
|
|
|
#5 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Quote:
La coda circolare avrà M risorse. Inizializzerai due QSemaphore vuote(M), piene(0), una mutex(unlocked) da condividere fra i produttori. Un vettore: Risorse buffer[M]: int inserimento = 0; int prelievo = 0; Per l'inserimento: 1 - acquisisci vuote 2 - acquisisci la mutex 3 - buffer[inserimento] = miaRisorsa 4 - inserimento = (inserimento + 1) % M; 5 - release sulla mutex 6 - release su piene Per il prelievo: 1 - tento l'acquisizione di piene, se fallisco ritorno al chiamante, non ci sono risorse disponibili 2 - se sono qui significa che ci sono risorse disponibili 3 - risorsaDaRitornare = buffer[prelievo]; 4 - prelievo = (prelievo + 1) % M; 5 - release su vuote Se noti prelievo e inserimento non si troveranno mai ad operare sullo stesso elemento di buffer, di conseguenza non c'è concorrenza su buffer. Non c'è concorrenza nemmeno sugli incrementi. La concorrenza è solo fra inserimenti diversi (dato che non ci sono prelievi contemporanei) e viene gestita dalla mutex. Questo è il classico esempio di coda circolare che è presente nella letteratura per la sincronizzazione fra processi |
|
|
|
|
|
|
#6 | |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Mi sa che non fa per il caso mio ...
Io ho un singolo processo (che poi saranno 15 perchè avremo 15 playback clients) che ha solo 2 threads, uno capo, che decide cosa fare, ed uno "schiavo", che esegue. Quindi ci deve essere concorrenza perchè se il capo decide che lo schiavo deve fermarsi, questo si deve fermare fino a cambio ordine. Però è interssante questa coda circolare, ogni volta che intervieni tu ne imparo una nuova. Quote:
|
|
|
|
|
|
|
#7 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
E' merito delle tue domande che sono sempre interessanti
Per produttore si intende colui che produce una risorsa (i comandi nel tuo caso), per consumatore si intende colui che consuma la risorsa (esegue i comandi). Quindi hai un solo produttore e un solo consumatore, è ancora più semplice !!! Inizializzerai due QSemaphore vuote(M), piene(0): Comando buffer[M]: int inserimento = 0; int prelievo = 0; Per l'inserimento: 1 - tento l'acquisizione di vuote, se fallisco ritorno al chiamante perché non ci sono risorse disponibili 2 - buffer[inserimento] = mioComando 3 - inserimento = (inserimento + 1) % M; 4 - release su piene Per il prelievo: 1 - acquisisco piene (mi sembra di capire che se non ci sono comandi il consumatore si debba fermare) 2 - comandoDaEseguire = buffer[prelievo]; 3 - prelievo = (prelievo + 1) % M; 4 - release su vuote 5 - ritorno comandoDaEseguire al chiamante Credo che questa cosa sia perfetta per te. In realtà ci sono due mutex, ma sono nascoste nei due semafori, però solo un processo si può bloccare su ogni mutex e non succede mai in contemporanea. Di fatto quando il consumatore ha finito i comandi si blocca autonomamente sul semaforo fino a quando il produttore non inserirà un nuovo comando. |
|
|
|
|
|
#8 | |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Scusa ma non ho capito questo
Quote:
?
|
|
|
|
|
|
|
#9 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
|
|
|
|
|
|
#10 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Considera il semaforo come un contatore, ogni volta che rilasci il semaforo lo incrementi, ogni volta che lo acquisisci lo decrementi, se il contatore è zero e tento di fare un'acquisizione il mio thread si blocca in attesa che torni maggiore di zero.
|
|
|
|
|
|
#11 | ||
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Quote:
sapevo che è il resto della divisione ma non riuscivo a capire la sua utilità! Quote:
![]() Domani a mente fresca riguardo il tutto. Grazie mille |
||
|
|
|
|
|
#12 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Non ti preoccupare, il meccanismo è fine, ma è semplice
Per una miglior comprensione sostituisci la release con nomeSemaforo++ e l'acquisizione con nomeSemaforo-- (tieni conto che non può mai arrivare sotto zero). Ovviamente solo per fare i conti su carta Ultima modifica di cionci : 26-01-2009 alle 16:30. |
|
|
|
|
|
#13 |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Ok, sono lucido e riposato
Mi sa che c'è qualcosa di troppo nella tua soluzione ... Non è che "se non ci sono comandi il consumatore si deve fermare" ma dipende dall'ultimo comando ottenuto. Immagina un cd player: premi "play" e il cd parte; fino a che non riceve un altro comando (o non finisce il cd) il cd viene riprodotto. Poi viene premuto "pause", nuovo comando, e il cd si ferma - fino ad un nuovo comando il cd sta fermo. etc etc. Quindi non capisco l'utilità della coda circolare. Probabilmente mi basta un mutex che blocca una variabile "comando" |
|
|
|
|
|
#14 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Tu hai chiesto una soluzione per evitare che i due thread si sincronizzassero. Questa è una soluzione. Ad evitare che il consumatore si blocchi ci vuole poco (tryAcquire al posto di acquire).
Spiegami qualcosa di più sui comandi. Se il thread produttore produce molti comandi. E possibile che ci siano più comandi in attesa di essere eseguiti ? Se la risposta è sì, allora ti serve ad ogni costo una coda circolare. A meno che non voglia far bloccare il tuo produttore su una mutex fino a quando il consumatore non ha consumato il comando Io la vedo così. Quello che mi immagino è che il produttore possa produrre molti comandi e che il consumatore debba fare "qualcosa" con questi comandi, questo "qualcosa" occupa tempo e quindi non rende il consumatore reattivo al massimo ai comandi. Il produttore non si può permettere di fermarsi ad aspettare che il consumatore esegua il comando, ma nemmeno il consumatore (visto che dovrai fare un playback dovrai rispettare temporizzazioni strette). Per garantirti la massima reattività ai comandi: Nuova architettura. Thread A: produttore Thread B: consumatore Thread C: playback Coda come sopra: bloccante per il consumatore e non bloccante per il produttore (la coda non si dovrebbe riempire mai, tranne per errori, quindi bene o male se la tryAcquire fallisce dovresti generare un'eccezione). Con questa architettura è palese che quando non ci sono comandi il consumatore resta sempre in attesa sul semaforo del prelievo. Quindi il consumo di uno comando è veramente istantaneo. Il produttore ha attesa zero sulla coda perché è praticamente sempre vuota. All'inserimento di un comando il consumatore viene risvegliato e preleva immediatamente il comando. All'uscita dal prelievo lo esegue su playback: se deve stoppare il playback acquisirà una mutex, se deve avviare il playback rilascerà la stessa mutex, se deve fare altre cose lo saprai te cosa deve fare Il consumatore non appena ha eseguito il comando si rimette in attesa sul prelievo fino a quando non riceve un altro comando. L'interfaccia migliore fra consumatore e playback al di là di come te la ho descritta sopra dovrebbe essere questa: consumatore: prelevo comando dalla coda acquisisco MutexM setto il comando se è il comando stop acquisisco MutexStop se è il comando play rilascio MutexStop rilascio MutexM playback: acquisisco MutexM se non ci sono comandi continuo ad eseguire quello attuale, salto al rilascio recupero il comando se è il comando di stop, rilascio MutexM, acquisisco MutexStop (qui si ferma), acquisisco MutexM, comando = Play rilascio MutexM eseguo il comando che mi è stato impartito Alla fine: se per il tuo produttore è accettabile l'attesa fra l'acquisizione ed il rilascio di MutexM allora puoi tranquillamente sostituire il produttore al consumatore mantenendo un'architettura a 2 thread. Ultima modifica di cionci : 27-01-2009 alle 11:19. |
|
|
|
|
|
#15 | |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Mamma mia, come al solito mi hai dato parecchi input
Adesso provo un pò e poi vedo quale soluzione prendere (sicuramente ho già abbandonato l'orribile idea di un mutex per ogni comando, grazie). Diciamo che l'attesa tra l'acquisizione ed il rilascio è accettabilissima (anche perchè ho carta bianca quindi decido io Prima facevo un blocco mutex e lettura da file ogni secondo ma poi sono arrivato ad 1/4 di secondo. Quindi dal momento in cui premo stop a quello in cui il playback si ferma passano 2.5 decimi di secondo, direi che è impercettibile. Quote:
)PS: Ieri sera (come tutte le sere) suonavo la mia chitarra elettrica ed ho fatto caso al Cubase (programma professionale): quando premi start e stop c'è sempre un periodo piuttosto lungo di attesa; sullo start è ovvio perché ogni instrumento ha la sua latency, ma sullo stop immagino sia per lo stesso mio motivo (ovvero "attesa tra l'acquisizione ed il rilascio"). Quindi ho pensato: se lo fa una grossa società come la Steinberg lo posso fare anche io |
|
|
|
|
|
|
#16 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Ok, allora vai di metodo a 2 thread.
E' importante la sequenza di acquisizione e rilascio delle mutex Evita deadlock |
|
|
|
|
|
#17 |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Comuqnue io verificherei cosa avviene se vengono premuti due tasti contemporaneamente, probabilmente in questo modo rimani un po' indietro, mi spiego:
- tempo 0: premo play e stop (lo stop leggermente dopo) - tempo 250ms: premo play e stop (lo stop leggermente dopo) - tempo 500ms: premo tasto "Guerra termonucleare globale" Il programma dovrebbe reagire in questo modo: - tempo 20ms: leggo play - tempo 270ms: leggo stop - tempo 520ms: leggo play - tempo 1020ms: leggo stop - tempo 1270ms: leggo "Guerra termonucleare globale" Ho ritardato di 3/4 di secondo la fine del mondo |
|
|
|
|
|
#18 | ||
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
Quote:
Codice:
class RingBuffer class WavReader : public RingBuffer, QThread class WavTransport : public QThread class WavPlayer : public QThread Quote:
|
||
|
|
|
|
|
#19 |
|
Senior Member
Iscritto dal: May 2006
Città: Wursteland
Messaggi: 1749
|
visto che ci siamo faccio un'altra domanda:
15 playback clients che riproducono 15 diversi audio file, sono perfettamente sincronizzati ? non intendo l'audio in se ma proprio i tempi di esecuzione dei threads dei 15 processi. |
|
|
|
|
|
#20 | |
|
Senior Member
Iscritto dal: Apr 2000
Città: Vicino a Montecatini(Pistoia) Moto:Kawasaki Ninja ZX-9R Scudetti: 29
Messaggi: 53971
|
Sopra era "passaggio del testimone"
Quote:
Puoi usare una QWaitCondition. Codice:
mutex.lock();
numberOfThreadInside++;
if(numberOfThreadInside < 15)
{
allThreadInside.wait(&mutex);
}
else
{
allThreadInside.wakeAll();
}
numberOfThreadInside--;
mutex.unlock();
Ultima modifica di cionci : 27-01-2009 alle 16:14. |
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 22:33.












?

)








