PDA

Visualizza la versione completa : [C++] Terminare un thread nel modo corretto


misterx
03-12-2014, 22:38
ciao,
ho un programma il quale manda in esecuzione un thread che richiama un certo numero di funzioni scritte da me. Tra le funzioni ve né una che apre un file, scrive alcuni dati e lo chiude.
Nel programma è implementata anche la funzione di terminazione del thread ma temo sia scritta in modo sporco difatti, fermo il thread con la TerminateThread().

Il problema è che a volte il file rimane aperto, chiedo a chi ha esperienza nell'uso dei thread in Windows qual è la funzione corretta che termina il thread solo quando ha terminato eventuali scritture u disco o eventualmente, qual è la tecnica usuale per fermare un thread in modo sicuro.

grazie

MItaly
04-12-2014, 02:45
TerminateThread non va usata mai, dato che interrompe il thread esattamente là dove si trova in questo momento, senza rilasciarne le risorse; se ti va particolarmente male, potresti interromperlo mentre è nell'allocatore, e a quel punto qualunque new/malloc da tutti gli altri thread bloccherebbe il programma.

Non esiste una funzione di sistema per terminare un thread in maniera "pulita", anche solo per il fatto che il sistema non ne sa nulla delle risorse possedute dal tuo thread: la maggior parte delle risorse di sistema sono legate al processo, non al singolo thread, per non parlare delle risorse della tua applicazione (heap, connessioni a DB, ...), di cui il sistema non sa niente.

Di conseguenza, il modo corretto per far terminare il thread B dal thread A è fare in modo che A "parli" a B e gli dica di terminare, e questo lo faccia (facendo la return dalla funzione da cui è partito). Uno dei modi più semplici per fare ciò è semplicemente avere un flag condiviso (di un tipo atomico) che viene ciclicamente controllato nel thread B e, in base al suo valore, prosegue o termina.

In ogni caso, in molti casi - e specie in una situazione di tipo "producer-consumer" (un thread produce dei job da fare, gli altri thread in parallelo ci lavorano) - usare i thread a così basso livello è inutilmente complicato (oltre che inefficiente se lanci troppi thread), può essere un'idea migliore usare librerie che ti forniscano astrazioni di più alto livello (thread pool, map-reduce, job queues, message passing, modello ad "attori", ...).

misterx
04-12-2014, 10:29
grazie per la risposta molto completa.

misterx
04-12-2014, 13:11
ne approfitto per analizzare due modi d'uso del codice sotto riportato, il primo prevede mettere la variabile bAbort a true e sucessivamente chiamare la TerminateThread(); la seconda invece chiamare solo la TerminateThread().
Nel primo caso la funzione Scrivi() dovrebbe essere eseguita completamente sino alla chiusura del file mentre nel secondo tutto si può dire: che ve ne pare?

grazie



DWORD MyThread()
{
while(bAbort==true)
{
Scrivi();
}
}

void Scrivi()
{
FILE *fp;
fp=fopen("test.txt","a+");
fprintf(fp,"test\n");
fclose(fp);
}

MItaly
04-12-2014, 23:17
Credo tu intenda


while(!bAbort)

:stordita:

misterx
04-12-2014, 23:32
eh si, errore mio

misterx
09-12-2014, 08:08
ho un altro dubbio: il thread attende sempre la terminazione della funzione Scrivi() oppure continua a chiamare e vengono accodate le chiamate alla funzione Scrivi() ?
Ho inserito una variabile "aperto/chiuso" per vedere se ogni volta viene chiuso il file, ma se esistono chiamate "accodate" di tipo Scrivi() come consigliate di scoprirlo?
Incremento una variabile globale nel thread che decremento nella funzione e se supera il valore 1 allora esiste un accodameno di chiamate?

grazie



DWORD MyThread()
{
while(!bAbort)
{
Scrivi();
}
}

void Scrivi()
{
FILE *fp;
fp=fopen("test.txt","a+");
fprintf(fp,"test\n");
fclose(fp);
}

MItaly
10-12-2014, 00:09
Un thread non è magico, è semplicemente un flusso di esecuzione parallelo rispetto a quello "principale", all'interno continuano a valere le "normali" regole del linguaggio: se fai una chiamata a funzione naturalmente sarà sincrona.
Quello che invece è estremamente complicato e assolutamente controintuitivo sono le interazioni tra thread diversi, specie con più CPU in ballo:
- se hai più thread che scrivono una stessa struttura dati senza alcuna regolamentazione è praticamente garantito che nel giro di breve iniziano a succedere crash "impossibili", dovuti a race conditions nei tuoi algoritmi; lo stesso dicasi per chiamate a funzioni di libreria non garantite thread safe;
- lo scheduler del sistema operativo può effettuare un context switch in qualunque momento, compreso a metà di una tua istruzione (il thread viene messo in pausa sulla singola istruzione assembly, per cui anche a metà della valutazione dell'equivalente espressione C);
- solo determinati tipi sono atomici, per cui se avviene un context switch ben piazzato in memoria potrebbe essere stato copiato solo metà del tuo dato;
- il compilatore può riordinare le scritture e le letture all'interno di una funzione, cachare l'accesso ad una variabile in un registro e similari, per cui l'ordine che hai scritto può non essere necessariamente rispettato nel codice generato;
- il processore stesso riordina l'esecuzione di letture e scritture per massimizzare il throughput;
- le scritture in cache impiegano del tempo a propagarsi tra un core e l'altro - non necessariamente gli N core hanno una visione "coerente" del mondo.
In sintesi, se inizi ad avere dati condivisi tra due thread entri in un mondo di dolore. Tutti queste "occasioni di asincronia" possono essere regolate con costrutti appositi di basso livello (fences per il compilatore, memory fances per il processore, istruzioni con prefisso LOCK, tipi atomici, ...), ma, almeno per iniziare, quello che devi assolutamente imparare è l'uso di lock (mutex, critical sections) per garantire che alle risorse condivise acceda un solo thread per volta (e stare attento ad acquisire i lock nell'ordine corretto per evitare deadlock).

misterx
10-12-2014, 08:08
grazie per l'esposizione approfondita, il dubbio mi era venuto quanto in modo del tutto casuale, viene segnalato un problema di stack overflow per un thread che fa le stesse cose dell'esempio che ho postato, scrive un file e non vi è nessuna concorrenza. Il programma a scopo di test rimane in esecuzione per settimane e di tanto in tanto si blocca segnalando errore di stack che non si riesce a stanare.

MItaly
10-12-2014, 08:47
Posta il codice in questione.

Loading