PDA

Visualizza la versione completa : [C] Lock su stream e/o su file


closerToTheEdge
31-01-2012, 19:41
Come posso fare in modo che più thread possano scrivere su uno stream (cioè un FILE *) senza fare interleaving con il loro output?

NON so a priori quanti sono i thread.
Ogni thread conosce il nome del file su cui scrivere in modo interattivo DOPO che è partito.
C'è quindi la possibilità che due o più thread vadano a scrivere sullo stesso file, ma non si sa a priori.

In pratica, quello che cerco è un lock che possa funzionare a livello di thread.
Ho letto in giro che esistono funzioni come flockfile() ... l'ho testato, ma sembra che non sia sufficiente.
Credo che la motivazione risieda nel fatto che flock() imponga un lock sullo stream, ma ogni thread apre il SUO stream, seppur verso lo stesso file fisico ...

Quindi, come si può fare?
E' possibile lockare proprio il file, in modo che chiunque tenti di aprire uno stream verso di esso se ne accorga?

shodan
31-01-2012, 21:33
L'ideale è che ogni thread abbia un suo file, il cui nome sia la concatenazione tra il nome immesso e l'id del thread che ci deve lavorare. Altrimenti lock o non lock, la prima fopen( stesso_nome ,"w") + fwrite() rischia di resettartelo.
Un'alternativa può essere l'apertura in modo esclusivo (non in share mode) del file. In questo caso solo il primo thread che apre il file è autorizzato dal SO a scriverci.
Questo però richiede l'uso delle API del SO (open()/write() ?) e non del C, dal momento che le API C sono solo una parziale astrazione di quello che c'è sotto.

closerToTheEdge
31-01-2012, 22:20
Ti ringrazio!

Purtroppo, però, non è possibile che ogni thread apra il proprio file.
E' un'applicazione distribuita, e il client deve poter decidere di scrivere su un file sul server (che devo implementare). Perciò se due client vogliono scrivere sullo STESSO file, devono poter farlo.

Invece, ipotizzando di voler lavorare con i file descriptor piuttosto che con gli stream, con una chiamata a flock() si potrebbe fare?
Voglio dire, mi sembra di aver capito che la flock() agisca sul file, e non sul descrittore restituito dalla open().
Perciò, se OGNI thread invoca la open() sul file, flock() dovrebbe garantire la mutua esclusione sullo stesso.
E' così?

shodan
31-01-2012, 22:35
A basso livello tutto funziona con il file descriptor se è per quello.
Mi pare di intuire che tu stia lavorando con Linux.

Edit...

http://www.lilik.it/~mirko/gapil/gapilpa1.html#gapilch9.html

La sezione 11.4 parla del file locking, mentre come recuperare il file descriptor è in 7.3.1

A naso però non credo sia sufficiente.

ramy89
01-02-2012, 00:09
E i thread non possono comunicare tra di loro per "dire" agli altri se un file è occupato?
Se no, potresti provare a mettere un primo carattere che serve solo a dire se il file è occupato o no, ogni thread apre il file in lettura e se il file non è occupato cambia quel valore a 1, se è occupato aspetta.Non so se può funzionare ...

linoma
01-02-2012, 08:07
A mio avviso dovresti fare in modo che solo il thread principale possa scrivere su file.

Javino89
01-02-2012, 10:20
Si può si può. Prova ad approfondire il concetto di concorrenza e sincronizzazione. Code di messaggi o semafori! Però ora che ci penso comunque ci sarebbe interleaving. Un thread alla volta potrà scrivere.

closerToTheEdge
01-02-2012, 11:22
Ancora grazie per le risposte!

Sto lavorando su MAC OS X che comunque è di derivazione UNIX-BSD.

Da quanto ho letto sulla flock()

un lock, qualunque sia l'interfaccia che si usa, anche se richiesto attraverso un file descriptor, agisce sempre su un file; perciò le informazioni relative agli eventuali file lock sono mantenute a livello di inode.

Perciò se un thread apre il proprio file descriptor ed un secondo ne apre un altro verso lo stesso file (e quindi i file descriptor puntano allo stesso inode), quando uno dei due acquisisce un lock, se il secondo tenta di farlo troverà che nell'inode c'è già un lock attivo, e quindi si metterà in coda.
Quindi se starò attento nell'implementazione dei lock, acquisendoli e rilasciandoli in modo corretto in ogni thread, mi sembra che potrebbe funzionare.
Dico bene?

Ai semafori avevo pensato: ogni file dovrebbe avere il proprio semaforo associato.
Ma non sapendo a priori il numero dei file, non riesco ad implementare una simile soluzione.

Inserire un carattere di controllo all'inizio del file non mi garantisce la mutua esclusione, in quanto leggere/scrivere e confrontare sono azioni che non si possono svolgere atomicamente.

shodan
01-02-2012, 13:40
Più che altro dovresti chiarire cosa vuoi ottenere.
Supponendo che:

il thread 1 debba scrivere "aa"
il thread 2 debba scrivere "bb"
il thread 3 debba scrivere "cc"

non hai nessuna garanzia di avere in uscita:
"aabbcc"
ma puoi trovarti con combinazioni tipo:

"ccaabb"
"bbccaa"
etc.

In più le fwrite aggiornano il FILE*, passato come parametro sulla posizione corrente nel file, sulla posizione corrente in scrittura del file. Per cui se non sono aggiornati tutti e tre rischi di trovarti solo:
"aa"
"bb"
"cc"
nel file.

L'unica strada che mi pare percorribile è aprire il file tramite API di sistema passando alla open il flag di possesso esclusivo.
Gli altri thread che cercano di aprire il file devono uscire se non riescono ad aprirlo.
In altre parole il lock dev'essere a livello SO, non utente.
Da questo punto di vista, un file non è diverso da una seriale. Solo un processo/thread può aprire la stessa seriale: gli altri devono fallire.

closerToTheEdge
01-02-2012, 18:37
Si scusate, ho omesso di spiegare che l'ordine in cui i thread scrivono non è importante, ma è fondamentale che ogni thread termini di scrivere i propri dati prima che un altro inizi a scrivere i suoi.

Ho provato a fare un pò di test con flock() e diversi file descriptor ottenuti dai thread (tutti che si riferiscono allo stesso file) e ho visto che tutto sembra funzionare.
Gli stessi test con flockfile() su un FILE * fallivano.
Ogni thread scrive i propri contenuti, e gli altri rimangono in coda; succede che i thread non scrivano necessariamente nell'ordine in cui sono invocati, ma ognuno scrive tutto quanto ha da scrivere prima di lasciare il posto al successivo, che fa la stessa cosa, e questo mi sta bene.

L'unica "difficoltà" sta nel fatto che con una fprintf() potevo fare direttamente output formattato su file; ora invece devo passare prima per una sprintf() per formattare la mia stringa, e poi passarla alla write().

Avevo inizialmente optato per gli stream offerti dalla libreria perchè ho letto che implementano intrinsecamente funzionalità di locking sul singolo stream; il MIO errore è stato quello di non capire subito che nel mio caso ogni thread apre il suo stream che, sebbene punti allo stesso file, è comunque differente dagli altri.

Agendo a basso livello sull'inode, sembra tutto risolto!

Grazie mille!

Loading