PDA

View Full Version : [C/C++] problemi coi thread


misterx
12-04-2011, 19:37
ciao,
ho due thread che ho chiamato come di consueto PRODUTTORE e CONSUMATORE.

Il produttore popola una array mentre come viene ovvio pensare CONSUMATORE lo usa.

Ho settato una variabile semaforica in modo che il CONSUMATORE non può accedere all'array sino a quando questo non è aggiornato completamente e da qui il problema: come faccio a fare in modo che l'aggiornamento non avvenga a CPU time ma ad esempio ogni 50 ms mentre il consumatore fa la sua parte ogni 250 ms ?

Essendoci in mezzo il semaforo se ritardo il produttore di conseguenza ritardo anche il consumatore ma desidererei tenere separati i due thread in modo che i rispettivi "lavori" avvengano in tempi differenti per mantenendo i vincoli dell'accesso all'array da parte del consumatore.

grazie

misterx
12-04-2011, 20:56
edit

banryu79
13-04-2011, 13:46
Risposta banale (spero di non suggerirti cavolate): fai eseguire a entrambi i thread (quando non hanno il lock sull'array ovviamente) una sleep; prodottore dormirà 50ms, consumatore dormirà 250ms.

misterx
13-04-2011, 15:06
Risposta banale (spero di non suggerirti cavolate): fai eseguire a entrambi i thread (quando non hanno il lock sull'array ovviamente) una sleep; prodottore dormirà 50ms, consumatore dormirà 250ms.

ho provato questa strada ma mi sonon accorto che i due thread attendenndo reciprocamente il semaforo verde, sono soggetti entrambi al tempo più lungo.
Magari è la mia implementazione ad essere bacata.

semaforo=0;
thread_1
{
if(semaforo==0)
{
popola array;
}
semaforo=1;
}

thread_2
{
if(semaforo==1)
{
consumaa array;
}
semaforo=0;
}

marco.r
13-04-2011, 15:10
ciao,
ho due thread che ho chiamato come di consueto PRODUTTORE e CONSUMATORE.

Il produttore popola una array mentre come viene ovvio pensare CONSUMATORE lo usa.

Ho settato una variabile semaforica in modo che il CONSUMATORE non può accedere all'array sino a quando questo non è aggiornato completamente e da qui il problema: come faccio a fare in modo che l'aggiornamento non avvenga a CPU time ma ad esempio ogni 50 ms mentre il consumatore fa la sua parte ogni 250 ms ?

Essendoci in mezzo il semaforo se ritardo il produttore di conseguenza ritardo anche il consumatore ma desidererei tenere separati i due thread in modo che i rispettivi "lavori" avvengano in tempi differenti per mantenendo i vincoli dell'accesso all'array da parte del consumatore.

grazie

Il produttore gira a frequenza quadrupla rispetto il consumatore: va bene che il produttore acceda solo alla versione piu' recente, e "perda" le tre precedenti ? O deve poter accedere anche alle elaborazioni intermedie ?

Nell'ipotesi che sia vera la prima affermazione, e' relativamente semplice:
devi avere due scheduler distinti nel primo e secondo thread. Uno impostato a 250ms e l'altro a 50ms. Se il tuo ambiente non te fornisce si puo' crearne uno a base di (sotto unix) clock_gettime e clock_nanosleep (o le equivalenti sotto windows). Devi solo tenere presente che non puoi fare semplicemente una sleep di 50ms, ma devi tenere conto di quanto dura il singolo loop
Fatto questo, non devi far altro che fare una cosa come la seguente:



// producer
scheduler sched(50000); // usec
while(!done)
{
do_something();
lock_array();
perform_changes();
unlock_array();
sched.wait_next();
}

// consumer
scheduler sched(250000); // usec
while(!done)
{
lock_array();
read_changes();
unlock_array();
do_something_else();
sched.wait_next();
}

Tieni presente che idealmente devi solo copiare i dati necessari da/verso l'array, mentre lo blocchi. Non vuoi che il produttore aspetti 250ms frattanto che il consumatore fa i cavoli suoi bloccandoti l'array.

marco.r
13-04-2011, 15:12
ho provato questa strada ma mi sonon accorto che i due thread attendenndo reciprocamente il semaforo verde, sono soggetti entrambi al tempo più lungo.
Magari è la mia implementazione ad essere bacata.

semaforo=0;
thread_1
{
if(semaforo==0)
{
popola array;
}
semaforo=1;
}

thread_2
{
if(semaforo==1)
{
consumaa array;
}
semaforo=0;
}
Come dicevo sopra devi tenere bloccato l'array per il tempo minimo per copiare i dati. Meglio ancora, invece di usare un semafoto usa qualcosa di piu' alto livello tipo una coda di messaggi, e ottieni questo quasi automaticamente (ed il codice e' piu' pulito).

misterx
13-04-2011, 15:35
grazie marco.r

quello che interessa a me è avere sempre l'array aggiornato ma impedire che il consumatore vi acceda se non è stato completamente aggiornato.

Il semaforo risponde allo scopo ma i due thread viaggiano sincroni anzi, il più lento "rallenta" quello più veloce.

Sto sviluppando in Borland Builder C++

marco.r
13-04-2011, 15:45
grazie marco.r

quello che interessa a me è avere sempre l'array aggiornato ma impedire che il consumatore vi acceda se non è stato completamente aggiornato.

Il semaforo risponde allo scopo ma i due thread viaggiano sincroni anzi, il più lento "rallenta" quello più veloce.

Sto sviluppando in Borland Builder C++
Perche' per come usi tu il semaforo, il produttore aspetta che il consumatore abbia eseguito il suo loop.
Come ti suggerisco io invece, con u nsemplice lock, il produttore puo' bloccare/sbloccare l'accesso all'array quante volte vuole, ma essendo sincronizzato, cmq produttore e consumatore non possono accedervi contemporaneamente
per usare la tua notazione, dovresti usare qualcosa tipo

semaforo=0;
thread_1
{
popola_array();
sem_post(semaforo);
while( ... )
{
aspetta prossimi 50ms
sem_wait(semaforo);
popola array;
sem_post(semaforo);
}
}

thread_2
{
while( ... )
{
aspetta prossimi 250ms
sem_wait(semaforo);
consumaa array;
sem_post(semaforo);
}
}

misterx
13-04-2011, 18:25
Perche' per come usi tu il semaforo, il produttore aspetta che il consumatore abbia eseguito il suo loop.
Come ti suggerisco io invece, con u nsemplice lock, il produttore puo' bloccare/sbloccare l'accesso all'array quante volte vuole, ma essendo sincronizzato, cmq produttore e consumatore non possono accedervi contemporaneamente
per usare la tua notazione, dovresti usare qualcosa tipo

semaforo=0;
thread_1
{
popola_array();
sem_post(semaforo);
while( ... )
{
aspetta prossimi 50ms
sem_wait(semaforo);
popola array;
sem_post(semaforo);
}
}

thread_2
{
while( ... )
{
aspetta prossimi 250ms
sem_wait(semaforo);
consumaa array;
sem_post(semaforo);
}
}


non mi è chiara la tua implementazione di sem_wait(semaforo); e sem_post(semaforo);



int array[100];

thread_1
{
while(1)
{
Sleep(50);
for(i=0; i<100; i++)
array[i]=rand();
}
}

thread_2
{
while(1)
{
Sleep(250);
for(i=0; i<100; i++)
printf("%d\n",array[i]);
}
}



una cosa di questo tipo credo sia fuori controllo in quanto non saprei quando verranno eseguiti i due thread

misterx
13-04-2011, 19:19
ora ho capito cosa intendevi, così funziona molto bene

int array[100];

thread_1
{
while(1)
{
sem=0;
for(i=0; i<100; i++)
array[i]=rand();
sem=1;
Sleep(50);
}
}

thread_2
{
while(1)
{
if(sem==1)
{
for(i=0; i<100; i++)
printf("%d\n",array[i]);
}
Sleep(250);
}
}

marco.r
13-04-2011, 20:38
Non puoi usare una semplice variabile per la sincronizzazione. Innanzi tutto non puoi garantire che l atray venga copiato ogni 250 ms. Perché invece di attendere che venga finita la scrittura dal produce, aspetti la volta dopo. Potenzialmente se hai sfoga la copia non avviene mai. Peggio ancora eventuali ottizzazioni del compilatore possono causare problemi di sincronizzazione del valore del semaforo tra due thread.
Invece che un intero usa un semaforo vero e proprio o più precisamente un mutex. Li trovi tra le api di windows

misterx
13-04-2011, 21:14
ed haai ragione. Funzionava meglio la prima implementazione mentre questa seconda potrebbe avvenire che il produttore porta a termine sempre il suo compito ma quando termina e parte il consumatore, se il thread consumatore finisce il suo quantum di tempo a disposizione, si torna al produttore che inibisce il funzionamento del consumatore :stordita:

non ho mai usato i mutex :stordita:

misterx
14-04-2011, 07:18
ciao,
non sono molto convinto di come sto usando i mutex




int array[100];

/* PRODUTTORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
array[i]=rand();

ReleaseMutex(hMutex);
Sleep(50);

}


/* CONSUMATORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
printf(%d\n,array[i]);

ReleaseMutex(hMutex);
Sleep(250);

}





funziona a questo modo ?
Come lo verifico ?

grazie 1000

marco.r
14-04-2011, 08:15
ciao,
non sono molto convinto di come sto usando i mutex

Devi usare lo stesso mutex, oppure crearlo "named"(non ho idea di come si faccia sotto windows) usando lo stesso nome in entrambi i thread.

misterx
14-04-2011, 08:22
ma va creato un mutex globale allora ?

Oppure si crea un mutex PRODUTTORE e lo si usa in consumatore e viceversa?

Non mi è chiaro il meccanismo :stordita:

marco.r
14-04-2011, 08:33
Ti serve un unico mutex. Concettualmente e'come la variabile intera che usavi tu, solo che e' implementata opportunamente.

misterx
14-04-2011, 08:46
ho fatto cisì e sembra funzionare



int array[100];
hMutex=CreateMutex(NULL, FALSE,NULL);

/* PRODUTTORE */


while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

/* SEZIONE CRITICA */

for(int i=0; i<100; i++)
array[i]=rand();

/* FINE SEZIONE CRITICA */

ReleaseMutex(hMutex);
Sleep(50);

}


/* CONSUMATORE */

while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

/* SEZIONE CRITICA */

for(int i=0; i<100; i++)
printf(%d\n,array[i]);

/* FINE SEZIONE CRITICA */

ReleaseMutex(hMutex);
Sleep(250);

}

difatti se a PRODUTTORE o a CONSUMATORE elimino ReleaseMutex(hMutex); l'altro thread non viene mai eseguito.

Mi chiedo però se la parte di codice sottostante a WaitForSingleObject(hMutex,INFINITE); viene eseguita completamente sino al ReleaseMutex(hMutex); ?

marco.r
14-04-2011, 08:55
Mi chiedo però se la parte di codice sottostante a WaitForSingleObject(hMutex,INFINITE); viene eseguita completamente sino al ReleaseMutex(hMutex); ?

Non sono sicuro di aver capito cosa intendi dire.
Se e' quel che penso io, la risposta e' si', ovvero quando tu chiami WaitForSingleObject e qualcun altro l'ha gia' fatto, il thread resta in attesa finche' l'altro non chiama ReleaseMutex.
per cui solo uno alla volta dei due thread esegue la parte tra le due chiamate.

misterx
14-04-2011, 09:22
Non sono sicuro di aver capito cosa intendi dire.
Se e' quel che penso io, la risposta e' si', ovvero quando tu chiami WaitForSingleObject e qualcun altro l'ha gia' fatto, il thread resta in attesa finche' l'altro non chiama ReleaseMutex.
per cui solo uno alla volta dei due thread esegue la parte tra le due chiamate.

hai capito benissimo, grazie per la conferma. :)

misterx
18-04-2011, 05:59
c'è un aspetto che non avevo considerato: se ora devo riordinare in tempo reale una parte dell'array prima che venga usato da "consumatore" devo usare mutex ed un nuovo thread vero?

marco.r
18-04-2011, 08:45
c'è un aspetto che non avevo considerato: se ora devo riordinare in tempo reale una parte dell'array prima che venga usato da "consumatore" devo usare mutex ed un nuovo thread vero?
uhm dipende... "quando" lo devi riordinare ?
se deve essere fatto in un certo ordine (prima produco dei dati e poi li ordino) con il mutex non hai la garanzia che le cose vadano nell'ordine corretto (o cmq devi essere preciso nello specificare gli intervalli di partenza dei singoli loop), non puoi far fare l'operazione al producer ?
Ad esempio prima produci i valori su un array specifico del primo thread, li ordini e poi li copi nella zona condivisa.

misterx
18-04-2011, 10:23
uhm dipende... "quando" lo devi riordinare ?
se deve essere fatto in un certo ordine (prima produco dei dati e poi li ordino) con il mutex non hai la garanzia che le cose vadano nell'ordine corretto (o cmq devi essere preciso nello specificare gli intervalli di partenza dei singoli loop), non puoi far fare l'operazione al producer ?
Ad esempio prima produci i valori su un array specifico del primo thread, li ordini e poi li copi nella zona condivisa.

ciao,
dunque: leggo inizialmente un certo tipo di configurazione che mi indica un certo dispositivo ad esempio:

10.5
20.3
18.6
1.3
0.2

Tale configurazione mi rappresenta il byte nell'array e il rispettivo bit da interrogare.

Quindi: carico in memoria quella configurazione, leggo lo stato dei segnali (bit) dell'apparato e una volta stabilito quali bit cambiano, li ordino in ordine crescente di tempo; in questo modo posso conoscere la sequenza di attivazione dei segnali.

riassumendo

- leggo configurazione
- attivo produttore
- verifico con consumatore
- ordino per tempo di attivazione
- visualizzo

Nella fase di riordino potrebbero sorgere problemi se non si usa il mutex ?

marco.r
18-04-2011, 11:10
ciao,
dunque: leggo inizialmente un certo tipo di configurazione che mi indica un certo dispositivo ad esempio:

10.5
20.3
18.6
1.3
0.2

Tale configurazione mi rappresenta il byte nell'array e il rispettivo bit da interrogare.

Quindi: carico in memoria quella configurazione, leggo lo stato dei segnali (bit) dell'apparato e una volta stabilito quali bit cambiano, li ordino in ordine crescente di tempo; in questo modo posso conoscere la sequenza di attivazione dei segnali.

riassumendo

- leggo configurazione
- attivo produttore
- verifico con consumatore
- ordino per tempo di attivazione
- visualizzo

Nella fase di riordino potrebbero sorgere problemi se non si usa il mutex ?
Mi par di capire che il riordino lo fa il consumatore ?
Cmq si', ogni qualvolta accedi o modifichi i dati condivisi devi usare il mutex.
Hai due alternative: usi due volte il mutex, oppure copi i dati e poi ordini i dati copiati

misterx
18-04-2011, 11:30
Mi par di capire che il riordino lo fa il consumatore ?
Cmq si', ogni qualvolta accedi o modifichi i dati condivisi devi usare il mutex.
Hai due alternative: usi due volte il mutex, oppure copi i dati e poi ordini i dati copiati

ahi, e qui la cosa si complica.
Per riordinare uso due funzioni, la funzione A richiama la funzione B.
Questo perhè ho messo i dati di output in una tabella che va riordinata in base ai tempi di attivazione/disattivazione.


funzione_A()
{
rirdina righe scambiando tutte le colonne
}

funzione_B()
{
se cella_1 > cella_2 funzione_A
}

dovrei creare una sola funzione e mettere il mutex all'inizio?

marco.r
18-04-2011, 11:56
ahi, e qui la cosa si complica.
Per riordinare uso due funzioni, la funzione A richiama la funzione B.
Questo perhè ho messo i dati di output in una tabella che va riordinata in base ai tempi di attivazione/disattivazione.


funzione_A()
{
rirdina righe scambiando tutte le colonne
}

funzione_B()
{
se cella_1 > cella_2 funzione_A
}

dovrei creare una sola funzione e mettere il mutex all'inizio?
A naso direi che ti conviene mettere il mutex all'inizio di B e rilasciarlo alal fine di B
in questo modo anche durante la chiamata ad A l'array e' "lockato"

misterx
18-04-2011, 12:15
A naso direi che ti conviene mettere il mutex all'inizio di B e rilasciarlo alal fine di B
in questo modo anche durante la chiamata ad A l'array e' "lockato"

grazie,
ero in dubbio se mettere il mutex su entrambe le funzioni

misterx
18-04-2011, 12:54
altro dubbio,

se il thread_B viene invocato dal thread_C significa che con un singolo mutex ho garanzia che viene eseguito tutto il codice tra il mutex ed il suo rilascio?

Ad esempio, dopo aver eseguito PRODUTTORE (riempimento dell'array con lo stato dei segnali attuale) faccio la verifica con CONSUMATORE per vedere se lo stato dei segnali che mi interessano è cambiato.

Se un segnale è cambiato, allora richiamo la funzione RIORDINA in modo che venga posizionato il suo stato in tabella nella posizione corretta e quindi visualizzato il suo stato e tempo di attivazione in ordine crescente.

Se in consumatore ho:

START MUTEX

for(i=0; i < num_byte_desiderati; i++)
{
if(stato != statoprec)
{
riordina();

visualizza();
}
}
END MUTEX

riordina()
{
........
........
}

Quindi anche riordina viene bloccato dal mutex?

misterx
19-04-2011, 12:28
mi rispondo da solo ed è si :)

misterx
19-04-2011, 14:05
mi rispondo da solo ed è si :)

misterx
19-04-2011, 18:30
ma secondo voi acquisire segnali ogni 100 ms si può considerarlo real time se il sistema è in grado di reagire immediatamente?

marco.r
19-04-2011, 22:10
ma secondo voi acquisire segnali ogni 100 ms si può considerarlo real time se il sistema è in grado di reagire immediatamente?
Real-time non e' tanto una questione di velocita' o frequenza, ma quanto precise devono essere le tempistiche.
Visto che hai un vincolo di 8ms di ritardo massimo nella gestione del ciclo, direi di si'.

misterx
20-04-2011, 06:01
Real-time non e' tanto una questione di velocita' o frequenza, ma quanto precise devono essere le tempistiche.
Visto che hai un vincolo di 8ms di ritardo massimo nella gestione del ciclo, direi di si'.

grazie.

C'è un fatto un pò anomalo: ho contattao il costruttore degli apparati e mi ha detto che il tempo di risposta per il primo byte via ethernet è di 33 ms, se ne chiedo 2 il tempo sale a 34 ms e così via.

Facendo una richiesta di 128 alla volta ho settato un timer prima della lettura e dopo la lettura calcolandone il tempo che è al massimo di 16 ms considerando che lavoro su una rete piuttosto trafficata mentre la rete reale sulla quale lavorerà il programma sarà dedicata solo a questo. Mi chiedo come hanno determinato quei 33 ms quando io ne misuro al massimo 16 :stordita:

Chi ha ragione?

misterx
21-06-2011, 05:54
continuo sfruttando questo mio 3d.

Secondo voi, acquisendo i segnali via ethernet sto lavorando praticamente col la RAM e quindi parliamo in termini di tempo di ns (nanosecondi).

Se dopo ogni lettura e verifica dello stato dei segnali salvo su disco le differenze temporali, da ns mi sposto sui ms (millisecondi).

Volendo migliorare questo aspetto che cosa converrebbe fare ?

A) creare un apposito 3D per la memorizzazione
B) salvare i dati solo ogni tot tempo

Considerando che attualmente la scansione che sto facendo dei segnali riguarda 1 solo apparato e volevo aggiungerne altri 10 il che significa appesantire di molto il funzionamento del programma ed a me interessa avere lo stato dei segnali almeno ogni 50 ms.

Spero si siano capiti i miei dubbi

grazie

marco.r
21-06-2011, 08:29
continuo sfruttando questo mio 3d.

Secondo voi, acquisendo i segnali via ethernet sto lavorando praticamente col la RAM e quindi parliamo in termini di tempo di ns (nanosecondi).

Se dopo ogni lettura e verifica dello stato dei segnali salvo su disco le differenze temporali, da ns mi sposto sui ms (millisecondi).

Volendo migliorare questo aspetto che cosa converrebbe fare ?

A) creare un apposito 3D per la memorizzazione
B) salvare i dati solo ogni tot tempo

Considerando che attualmente la scansione che sto facendo dei segnali riguarda 1 solo apparato e volevo aggiungerne altri 10 il che significa appesantire di molto il funzionamento del programma ed a me interessa avere lo stato dei segnali almeno ogni 50 ms.


Andrei decisamente per l'opzione A (ed e' quello che faccio io di solito). Con le scritture su disco hai garanzie minime sui tempi di esecuzione delle varie chiamate, e queste comunque portano via diverso tempo nel momento in cui i dati vengono flushati su disco.
Quel che faccio io di solito e' dedicare un thread alla scrittura su disco, ed usare una coda di messaggi per mandare da un thread all'altro i dati da salvare. Funziona bene ed e' pure pulito dal punto di vista del design.

misterx
22-06-2011, 16:51
Andrei decisamente per l'opzione A (ed e' quello che faccio io di solito). Con le scritture su disco hai garanzie minime sui tempi di esecuzione delle varie chiamate, e queste comunque portano via diverso tempo nel momento in cui i dati vengono flushati su disco.
Quel che faccio io di solito e' dedicare un thread alla scrittura su disco, ed usare una coda di messaggi per mandare da un thread all'altro i dati da salvare. Funziona bene ed e' pure pulito dal punto di vista del design.


quindi scusa, rimpire un buffer nel thread A con un centinaio di dati da salvare e poi richiamare il thread B che salva i dati su disco oppure richiamare ad ogni dato il thread B dal thread A ?

Cos' facendo mi viene il dubbio che poi A attende la fine di B oppure non è così ?

Sarebbe meglio creare un thread B e verificare quanti dati ci sono nel buffer e quindi svuotarlo quando pieno?

banryu79
23-06-2011, 08:05
quindi scusa, rimpire un buffer nel thread A con un centinaio di dati da salvare e poi richiamare il thread B che salva i dati su disco oppure richiamare ad ogni dato il thread B dal thread A ?

Suppongo che marco.r ti stesse suggerendo di usare una coda di messaggi: il thread A (il produttore) infila un messaggio in testa alla coda continuamente; il thread B (il consumatore) prende l'ultimo messaggio dalla coda e lo processa, finchè c'è almeno un messaggio nella coda.

marco.r
23-06-2011, 11:07
Suppongo che marco.r ti stesse suggerendo di usare una coda di messaggi: il thread A (il produttore) infila un messaggio in testa alla coda continuamente; il thread B (il consumatore) prende l'ultimo messaggio dalla coda e lo processa, finchè c'è almeno un messaggio nella coda.

si', tutti i sistemi operativi moderni offrono una qualche funzionalita' del genere. Non e' niente di tragico, una semplice area di memoria protetta da mutex, il thread B rimane in attesa, il thread A scrive i dati e poi segnala l'evento e continua per la sua strada, il thread B copia i dati, li scrive dove deve scrivere e poi torna di nuovo in attesa.
Ovviamente se possibilie usa quelle gia' fornite da sistema, sono generalmente ben performanti e prive di bachi (ci sono alcuni corner case subdoli da tenere in consideazione)
Non so quale sia l'analogo Windows pero', solitamente lavoro sotto linux.

banryu79
23-06-2011, 11:14
Non so quale sia l'analogo Windows pero', solitamente lavoro sotto linux.
Da una veloce occhiata su MSDN punto sulle API PostThreadMessage e PeekMessage

misterx
23-06-2011, 18:29
si', tutti i sistemi operativi moderni offrono una qualche funzionalita' del genere. Non e' niente di tragico, una semplice area di memoria protetta da mutex, il thread B rimane in attesa, il thread A scrive i dati e poi segnala l'evento e continua per la sua strada, il thread B copia i dati, li scrive dove deve scrivere e poi torna di nuovo in attesa.
Ovviamente se possibilie usa quelle gia' fornite da sistema, sono generalmente ben performanti e prive di bachi (ci sono alcuni corner case subdoli da tenere in consideazione)
Non so quale sia l'analogo Windows pero', solitamente lavoro sotto linux.


alcune "finezze" di programmazione da voi suggerite non le ho ancora mai usate quindi sono un pochino perplesso e digiuno.

Rifacendomi al mio problema iniziale:

Thread A = produttore
Thread B = consumatore
Thread C = memorizzatore dei dati su disco

Mentre A riempie l'array, a B viene escluso l'accesso all'array tramite il mutex.
Quando B inizia a lavorare, riempie l'array e a questo punto C dovrebbe inizare a scriverlo sse vede in esso dei dati.

Mi chiedevo se scrivendo nel Thread C ad esempio:

if numero_dati_array > 20 allora scrivi su disco

oppure

B ha un dato da scrivere e con un: SendMessage_Thread C(dato da scrivere) sarebbe una soluzione migliore e non introdurrebbe alcun ritardo.

marco.r
23-06-2011, 19:02
alcune "finezze" di programmazione da voi suggerite non le ho ancora mai usate quindi sono un pochino perplesso e digiuno.

Piu' che una finezza secondo me e' proprio il modo che minimizza le possibilita' di errore di sincronizzazione, deadlock etc. E una volta capito e' semplice e facile da tenere a mente.


Rifacendomi al mio problema iniziale:

Thread A = produttore
Thread B = consumatore
Thread C = memorizzatore dei dati su disco

Mentre A riempie l'array, a B viene escluso l'accesso all'array tramite il mutex.
Quando B inizia a lavorare, riempie l'array e a questo punto C dovrebbe inizare a scriverlo sse vede in esso dei dati.

Mi chiedevo se scrivendo nel Thread C ad esempio:

if numero_dati_array > 20 allora scrivi su disco

oppure

B ha un dato da scrivere e con un: SendMessage_Thread C(dato da scrivere) sarebbe una soluzione migliore e non introdurrebbe alcun ritardo.
Nel primo caso puo' funzionare ma devi stare attento. Anche supponendo che il thread C tenga "fermo" il B solo per il tempo necessario alla copia dei dati dalla memoria condivisa a una memoria locale del thread, se per qualche motivo il thread C ci mette per una volta un tempo maggiore del doppio del tempo del loop del thread B, il thread B dovra' fermarsi per aspettare che il thread C "liberi" la memoria condivisa. Quindi devi stare usare piu' memorie condivise ( o una N volte piu' ampia di "20") a mo' di coda circolare. Comincia a diventare complicato. LA coda di messaggio opera in modo simile, ma si gestisce lei questi aspetti
(per inciso, penso che nel tuo caso piu' che la PostMessage, usata per mandare messaggi alle finestre, sia piu' adatto usare una pipe e poi le "solite" funzioni di lettura e scrittura da file. Nuovamente ci vuole pero' qualcuno piu' esperto nelle api di windows)

marco.r
23-06-2011, 19:04
Ah, in windows ci sono anche funzioni per la scrittura asincrona. Potrebbe essere una soluzione piu' diretta e adatta al tuo caso.

misterx
23-06-2011, 19:34
grazie marco.r e grazie anche all'altro utente per i suoi interventi.

Cercherò di approfondire la scrittura asincrona. Tutto questo problema nasce dal fatto che i Thread A e B lavorano in termini di nanosecondi mentre il Tgread C per via delle scritture lavora in ms.

Un ingegnere mia ha detto che gli apparati di cui mi sto occpando mi forniscono dati via ethernet ogni 33 ms anche se io onoestamente ho rilevato valori inferiori, meglio così.

Cosniderando che un ritardo tra i Thread A e B l'ho inserito io volutamente e dell'ordine di 20 ms ma penso che se dovessi scendere a 5 ms mi cambierebbe poco o nulla, ho il problema della scrittura su disco che devo cercare di eliminare in modo che la lettura e l'elaborazione dei tempi dei segnali ad opera dei Thread A e B si mantenga abbastanza fedele.

Provando a scrivere su disco i dati direttamente dal Thread B con le solite funzioni ho notato differenze in tempo di +/- 50 ms che credo sia proprio una differenza introdotte proprio dal disco.

marco.r
24-06-2011, 08:33
Provando a scrivere su disco i dati direttamente dal Thread B con le solite funzioni ho notato differenze in tempo di +/- 50 ms che credo sia proprio una differenza introdotte proprio dal disco.

Si'. Finche' scrivi i dati nei buffer tutto funziona bene, ma ogni tanto questi devono venire scritti su disco per cui hai il ridardo dovuto alla scrittura.
Poi non e' che quando la chiamata torna la scrittura fisica su disco e' avvenuta per forza, ma lo svuotamento del buffer comunque innesca dei meccanismi che portano un maggiore costo temporale (su un sistema operativo tradizionale perlomeno).

misterx
24-06-2011, 20:45
Si'. Finche' scrivi i dati nei buffer tutto funziona bene, ma ogni tanto questi devono venire scritti su disco per cui hai il ridardo dovuto alla scrittura.
Poi non e' che quando la chiamata torna la scrittura fisica su disco e' avvenuta per forza, ma lo svuotamento del buffer comunque innesca dei meccanismi che portano un maggiore costo temporale (su un sistema operativo tradizionale perlomeno).

e per aggirare tale problema si ricorre alla scrittura asincrona?

credo di aver trovato http://msdn.microsoft.com/en-us/library/aa365683(v=vs.85).aspx

misterx
15-07-2011, 16:31
ciao,
ora mi sono posto un ulteriore problema: se nei due thread produttore e consumatore elimino il mutex in quanto non desidero che un thread attenda l'altro ma viaggino in parallelo, potrei in questo caso avere problemi di collisione e cioè, potrebbe accadere che venga generato un errore se un thread accede ad una byte che l'altro thread sta scrivendo?
Sono tutte problematiche nuove per me e quindi ne so poco.
I thread condividono le risorse e mi verrebbe da pensare che in un sistema monoprocessore una collisione nel vero senso della parola non può avvenire ma in un sestema a più processori?

grazie

misterx
18-07-2011, 20:31
scusate ma non essendomi mai posto il problema: come istanzio n thread di questo tipo, magari 5 thread di tipo produttore ed altrettanti consumatore?


int array[100];

/* PRODUTTORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
array[i]=rand();

ReleaseMutex(hMutex);
Sleep(50);

}


/* CONSUMATORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
printf(%d\n,array[i]);

ReleaseMutex(hMutex);
Sleep(250);

}

tuccio`
18-07-2011, 20:58
se tu hai un buffer (un array nel tuo caso, mi pare di capire), deve essere acceduto in mutua esclusione, perché sia i produttori che i consumatori lo scrivono (producendo o consumandone un elemento).. quindi devi usare un (uno solo per tutti) mutex associato al buffer per accederlo in mutua esclusione

detto questo, nel tuo caso, visto che mi pare di capire che i thread sono nello stesso processo, è più opportuna una criticalsection (http://msdn.microsoft.com/en-us/library/ms682530%28v=vs.85%29.aspx), che è un tipo di mutex che può essere utilizzato all'interno di un solo processo ed è più efficiente del mutex di windows

marco.r
18-07-2011, 23:28
ciao,
ora mi sono posto un ulteriore problema: se nei due thread produttore e consumatore elimino il mutex in quanto non desidero che un thread attenda l'altro ma viaggino in parallelo, potrei in questo caso avere problemi di collisione e cioè, potrebbe accadere che venga generato un errore se un thread accede ad una byte che l'altro thread sta scrivendo?
Sono tutte problematiche nuove per me e quindi ne so poco.
I thread condividono le risorse e mi verrebbe da pensare che in un sistema monoprocessore una collisione nel vero senso della parola non può avvenire ma in un sestema a più processori?

grazie
una forma di sincronizzazione ti serve ,anche in un sistema monoprocessore.
questo perche' tu on puoi sapere quando la cpu decide di far girare un thread piuttosto che un altro, e quindi ottieni che puoi avere che un thread viene messo in pausa a meta' della scrittura della struttura condivisa.
Il problema non e' tanto i lfatto di dover sincronizzare, quanto il fatto che hai un'unica area di memoria, il che porta a dei blocchi se uno dei due thread per un certo periodo va piu' piano o piu' lento.
Quel che serve a te e' una struttura a cosa dove il primo thread possa accodare ad intervalli regolari i dati sa salvare su disco e l'altro se li prenda con tempi irregolari (dovuti alla scrittura su disco) senza per questo bloccare o rallentare il primo thread.

Per questo inizialmente ti avevo suggerito delle code di messaggi, perche' potrebbero farebbero proprio questo. Se quelle di windows non funzionano bene per te puoi adottare altre alternative analoghe... pipe o socket ad esempio, o ancora, se puoi usare le boost, le message queues di boost .
Alla peggio puoi usare un paio di mutex per proteggere una struttura dati tradizionale, tipo una lista di valori, anche se on lo consiglio perche' c'e' da stare attenti ad alcuni dettagli non banali.
Lo schema e' comunque quello: il thread a produce dei valori che mette nella coda, il thread b prende un elemento alla volta dalal coda e lo elabora.
Le due operazioni sono indipendenti, tranne quando il thread B deve aspettare perche' la coda e' vuota.
La cosa importante e' che il thread A non deve mai aspettare che il thread B finisca di elaborare. A meno che il thread B non sia sempre piu' lento per cui la coda si intasa... ma li allora il collo di bottiglia e' nell'hw.

marco.r
18-07-2011, 23:29
e per aggirare tale problema si ricorre alla scrittura asincrona?

credo di aver trovato http://msdn.microsoft.com/en-us/library/aa365683(v=vs.85).aspx

scusa visto solo ora.
dipende dal costo introdotto dalle api. se queste api sono analoghe alla sendfile di unix, sono pensate per spedire dati di una certa mole in backgrund per cui potrebbero essere troppo pesanti per il tuo uso... devi fare delle prove, o farti suggerire da qualcuno che ne sa :D

misterx
19-07-2011, 06:01
una forma di sincronizzazione ti serve ,anche in un sistema monoprocessore.
questo perche' tu on puoi sapere quando la cpu decide di far girare un thread piuttosto che un altro, e quindi ottieni che puoi avere che un thread viene messo in pausa a meta' della scrittura della struttura condivisa.

questo è un grosso problema al quale stavo pensando ultimamente.
In uno scenario con due thread ed un solo mutex, come dici tu stesso, non so assolutamente a quale dei due thread windows consente l'uso della CPU.
Non potrebbe accadere che terminati il thread produttore e rilasciato il mutex viene data ancora la CPU al medesimo thread in barba al thread consumatore?

A questo punto non mi converrebbe eliminare uno dei due thread e rendere sequenziale il thread stesso?

Infine potrei usare più array ed eseguire n thread con indirizzi IP differenti: ma questo sto cercando di capire come farlo.


p.s.
ho aggiunte due contatori nei due thread ed effettivamente in maniera del tutto casuale vengono eseguiti o più volte consumatore o più volte produttore :muro:

Quel che serve a te e' una struttura a cosa dove il primo thread possa accodare ad intervalli regolari i dati sa salvare su disco e l'altro se li prenda con tempi irregolari (dovuti alla scrittura su disco) senza per questo bloccare o rallentare il primo thread.

scusa se alcune cose le capisco molto dopo ma sono argomenti, quelli legati alle problematiche dei thread, piuttosto nuovi per me.
Non mi è chiaro come potrei ottenere una cosa del genere: potrei popolare un ulteriore array lato consumatore e far scivere su disco ad un terzo thread senza che consumatore venga rallentato?

misterx
21-07-2011, 05:13
altra diavoleria di windows: eseguendeo il codice qui sotto ho notato che i due thread vengono eseguiti un numero di volte diverso, a volte produttore viene eseguito 10 volte prima di cedere la CPU al consumatore :confused:

int array[100];

/* PRODUTTORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
array[i]=rand();

ReleaseMutex(hMutex);
Sleep(50);

}


/* CONSUMATORE */

hMutex=CreateMutex(NULL, FALSE,NULL);
while(.....)
{
WaitForSingleObject(hMutex,INFINITE);

for(int i=0; i<100; i++)
printf(%d\n,array[i]);

ReleaseMutex(hMutex);
Sleep(250);

}

marco.r
21-07-2011, 09:08
questo è un grosso problema al quale stavo pensando ultimamente.
In uno scenario con due thread ed un solo mutex, come dici tu stesso, non so assolutamente a quale dei due thread windows consente l'uso della CPU.
Non potrebbe accadere che terminati il thread produttore e rilasciato il mutex viene data ancora la CPU al medesimo thread in barba al thread consumatore?

Si', dipende da come opera lo scheduler del sistema operativo e da vari fattori anche esterni al tuo programma, anche se indicativamente piu' lunga e' l'esecuzione di un singolo "loop" piu' facile e' che vengano alternati.


A questo punto non mi converrebbe eliminare uno dei due thread e rendere sequenziale il thread stesso?

Puoi farlo ma ti ritorna il problema iniziale, ovvero quella della latenza dovuta alal scrittura su disco



scusa se alcune cose le capisco molto dopo ma sono argomenti, quelli legati alle problematiche dei thread, piuttosto nuovi per me.
Non mi è chiaro come potrei ottenere una cosa del genere: potrei popolare un ulteriore array lato consumatore e far scivere su disco ad un terzo thread senza che consumatore venga rallentato?

Devi avere una qualche struttura dati dove poter accumulare l'output del produttore finche' il consumatore non deve smaltirlo.
L'ideale sarebbe usare qualche struttura fornita dal sistema operativo o comunque da delle librerie.
Qui ti mostro un esempio di come funziona con boost::interprocess


typedef SomeThingBlaBlabla MyData;

// Produttore
message_queue mq(create_only, "my_message_queue",
100, sizeof(MyData));

while ( ... )
{
MyData array;
popola(array);
mq.send(&array,sizeof(array), 0);
}

// Consumatore

message_queue mq(create_only, "my_message_queue");

while ( ... )
{
size_t size;
unsigned int priority
MyData array;
mq.receive(&array, sizeof(array), size, priority);
if(size != sizeof(array))
throw SomeKindOfError();
}

Il primo thread "accoda" i valori prodotti con la "send" e questi vengono memorizzati "da qualche parte". Il secondo thread prende i valori accodati con la "receive", e resta in attesa se la coda e' vuota.

Volendo puoi fare anche una cosa con un mutex e un std::list<> (ad esempio) oppure con un array (di array, nel tuo caso) e un paio di cursori, pero' diventa un po' complicato gestire il caso di coda vuota .
Diverrebbe qualcosa tipo


typedef SomeThingBlaBlabla MyData;

const int MAX_QUEUE_SIZE = 1000;
MyData queue[MAX_QUEUE_SIZE];

// Produttore
while ( ... )
{
int cursor = 0;
MyData array;
popola(array);
lock_mutex();
queue[cursor] = array;
// signalQueueNoMoreEmpty()
unlock_mutex();
cursor = (cursor+1) % MAX_QUEUE_SIZE;
}

// Consumatore

while ( ... )
{
int cursor = 0;
MyData array;
lock_mutex();
if (queueIsempty())
waitForData() // will exit when the other thread calls signalQueueNoMoreEmpty() :P
array = queue[cursor];
unlock_mutex();
cursor = (cursor+1) % MAX_QUEUE_SIZE;
}

Come vedi e' gia' piu' complicato, e mancano una serie di controlli che nel caso precedente ti vengono gratis.
Inoltre tieni presente che con la coda puoi avere tranquillamente N thread produttori e M thread consumatori (se la cosa puo' tornare utile) che depositano e prelevano dalla stessa coda.

misterx
21-07-2011, 13:05
quindi riassumendo: attraverso i messaggi invio dati ad un terzo thread incaricato di scrivere su disco, magari allestendo un buffer sufficientemente ampio?


ciao

marco.r
21-07-2011, 13:23
quindi riassumendo: attraverso i messaggi invio dati ad un terzo thread incaricato di scrivere su disco, magari allestendo un buffer sufficientemente ampio?


ciao

Si' esatto
ripeto: se possibile usa un qualcosa tipo le message queues di boost oppure funzionalita' analoghe native di windows (che non conosco ma ci dovrebbero essere), ti eviti un sacco di lavoro e bugs. comunque se anche te lo fai tu il buffer concettualmente e' la stessa cosa.

misterx
21-07-2011, 14:18
Si' esatto
ripeto: se possibile usa un qualcosa tipo le message queues di boost oppure funzionalita' analoghe native di windows (che non conosco ma ci dovrebbero essere), ti eviti un sacco di lavoro e bugs. comunque se anche te lo fai tu il buffer concettualmente e' la stessa cosa.

uso borland builder c++, mi metto alla ricerca


grazie 1000

misterx
21-07-2011, 16:28
provo a anche a chiedere questa soluzione:

//thread produttore/consumatore

produce_dati();
legge_dati();
buffer_disco[...]=dato_cambiato;

in questo modo non accumulerei aventuali ritardi di windows che esegue n volte il thread produttoe prima di eseguire consumatore


//thread scrive dati su disco se trova dati
for(int i = 0; i < 1024)
fprintf(fp,"%d",buffer_disco[i++];


p.s.
proverò ad usare la PostMessage() solo per fretta ed in quanto non conosco la libreria da te citata