Newsgroup: it.comp.os.linux.development
Titolo: crosspost - thread e log

Salve a tutti, ho scritto qualcosa di simile in
comp.programming.threads ma mi pare un po' abbandonato come gruppo.
Perciò chiedo anche qui sperando di non essere troppo OT altrimenti
come non detto, chiedo scusa :)
Situazione: sto scrivendo in C un progetto multithread con i pthreads.
Scopo: ho creato una semplice funzione addlog(char *text) che tenta di
aprire un determinato file, ci scrive dentro (in append) il testo
passatole, chiude il file.
La funzione verrà richiamata in diversi momenti durante l'esecuzione
dei varii threads. Qualcosa del tipo:
int thread()
{
  int var;
  addlog("thread.c - thread() - Il thread è partito\n");
  /* do some stuff */
  addlog("thread.c - thread() - Sto per prelevare un parametro\n");
  var = getValue()
  addlog("thread.c - thread() - Parametro ricevuto\n");
  /* and so on... */
}
Domanda: che succede se durante l'esecuzione della funzione addlog(),
dopo l'apertura del file e prima della sua chiusura, l'esecuzione
passa da un thread ad un altro, che magari tenta di scrivere nello
stesso file?
Se l'apertura del file (in append) fallisse, non potrei fare il log,
se invece l'apertura del file non fallisse potrei trovarmi per caso un
file con due frasi miscelate?
Potrei fare un lock() prima della scrittura nel file ed una unlock()
dopo la sua chiusura, ma questo rallenterebbe un po' l'intera
esecuzione dei thread visto che i tentativi di log sarebbero molti,
quindi ci sarebbero molte probabilità che i thread si blocchino
vicendevolmente. Infatti se ho qualcosa come 100 thread, basta che uno
venga interrotto durante la addlog() perchè molti altri rimangano
sospesi durante il tentativo di scrivere anche loro nel file.
In teoria risolverei creando tanti file di log quanti sono i thread,
ma poi sarebbe facile scovare un bug?
Ripeto, chiedo scusa se son troppo OT.

 dopo la sua chiusura, 
[cut]
C'è poco da fare il file di log è una risorsa condivisa devi regolarne 
l'accesso in qualche modo.
Il fatto che la situazione di concorrenza potrebbe verificarsi spesso 
non deve farti pensare ai problemi di prestazioni nella serializzazione 
al contrario è proprio il motivo per cui devi mettere il semaforo! 
Altrimenti è altamente probabile che si incasini il file di log.
In ogni caso, se apri e chiudi il file ogni volta con modi diversi a 
seconda dell'operazione, il SO dovrebbe curare la concorrenza 
dell'accesso già di suo.

 dell'accesso già di suo.
L'unico pro, IMHO, è che, essendo l'operazione stessa di scrittura nel
file molto rapida, penso che pur bloccando altri processi sarebbe
comunque per tempi irrisori.
Vabè grazie, proverò.
In caso di scarse prestazioni posso sempre modificare quella singola
funzione per salvare su file diversi.

 comunque per tempi irrisori.
Tutto č relativo... dire che una scrittura su un file č molto rapida, 
secondo me č un'affermazione rischiosa.

 sospesi durante il tentativo di scrivere anche loro nel file.
beh, capisco la tua paura di finire in un deadlock, tuttavia se il 
thread che chiama addlog viene fermato dallo scheduler è anche l'unico 
la cui esecuzione possa riprendere. Basta che tu non vada a mettere 
nella addlog una condizione di blocco del thread che dipende da altri 
thread, perché questo sì è il modo più rapido per ficcarsi in uni stallo :-)

 thread, perché questo sì è il modo più rapido per ficcarsi in uni stallo :-)
Più che altro io mio timore è che all'ingresso della addlog() venga
eseguito un lock, il thread venga fermato e si blocchino tutti gli
altri thread che tentano di scrivere nel file di log, finchè
l'esecuzione non torna al thread "principale" che continuerà la
scrittura nel file e sbloccherà il mutex. A quel punto gli altri
thread potranno continuare (uno per volta) la loro scrittura.
Il ragionamento fila, i mutex fanno proprio questo e l'accesso
concorrente alle risorse condivise va fatto esattamente in questo
modo, lo so.
Ma la mia preoccupazione è: tanti thread, tantissimi tentativi di
scrittura nel file di log. Conseguenza: perdita di "parallelismo".

 Ripeto, chiedo scusa se son troppo OT.
Ciao  Manlio Perillo

 scrittura nel file di log. Conseguenza: perdita di "parallelismo".
E allora che dovresti dire di quei poveri N thread che bisticciano per 
avere un pò di CPU time per eseguire le loro istruzioni? :)
Ciao  Manlio Perillo

Manlio Perillo ha scritto:

Aggiungerei anche... che te ne fai di un file di log dove no si capisce 
niente?! Non si può sacrificare la corretezza del log in nome del 
parallelismo sfrenato!

 sbloccherà il mutex.
Te la butto li` en passant: crea un ennesimo thread che gestisce da solo 
il file di log. Gli altri thread useranno una coda o una pipe o una socket 
per passargli i messaggi e lui li scrive sul file.
In questo modo eviti di aprire/lockare/scrivere/unlockare/chiudere il file 
migliaia di volte al secondo.
Lo apri, lo locki una volta sola, poi ci scrivi n volte e infine unlocki e 
chiudi.
Tra l'altro cosi` bufferizzi meglio le scritture, e puoi anche tenere un 
buffer interno al thread.
Bye.

 socket per passargli i messaggi e lui li scrive sul file. 
Il problema con questa soluzione è che se la scrittura su file è lenta la 
coda di messaggi potrebbe crescere di molto.
Stai solo spostando il problema da un altra parte.
Ciao  Manlio Perilo



E` vero, ma con due vantaggi:
- si elimina l'overhead di apertura e locking, aumentando le prestazioni 
generali
- quando la coda si riempie, i thread "produttori" vengono messi in wait, 
mentre il thread "logger" che legge (e svuota) la coda viene attivato e 
puo` svuotare la coda. Tra l'altro, se non ricordo male le impostazioni 
dello scheduler, essendo un thread I/O-bound avra` priorita` maggiore di 
quelli CPU-bound.
L'importante e` non lasciar crescere troppo la coda, mettendoci un limite, 
e usare delle scritture bloccanti sulla coda nei thread, in modo che non 
vadano persi messaggi.
In pratica si aumenta il parallelismo e si diminuiscono gli overhead 
(teoricamente).
Bye.

 buffer non è troppo grande la scrittura dovrebbe essere atomica [1].
E' vero, non ci avevo pensato :)
Thanx!

 
Come detto, questo si risolve condividendo il descrittore di file, ed 
evitando il locking esplicito.
La write UNIX dovrebbe già garantire la scrittura atomica del buffer 
passato.
Comunque sul secondo punto mi informo su comp.std.unix.
Ciao  Manlio

 Bye.
Si, potrebbe essere una soluzione. Ma la lascio per un semplice
motivo: sarei punto ed a capo.
Per fare una cosa simile dovrei avere un thread che controlli se ci
son elementi occupati nella lista. Se ce ne sono, li scrive nel file e
svuota la lista.
I thread concorrenti se vogliono loggare qualcosa devono aggiungere i
loro elementi alla lista, che è una risorsa condivisa. Quindi ci
vorrebbe una funzione (per esempio sempre la addlog()) per appendere
le stringhe alla coda.
Solito discorso:
function addlog(char *text)
{
  lock di un mutex:
  aggiunta alla lista;
  incremento del contatore;
  unlock;
}
Tanti thread, tantissime chiamate ad addlog(), tantissime probabilità
che molti thread si blocchino (seppur per tempi brevissimi) solo per
poter loggare.
Senza considerare poi che le scritture sul file di log avverrebbero
solo durante l'esecuzione del thread di log, non più all'istante della
chiamata.
Quindi un eventuale crash di uno dei thread e quindi di tutto il
processo (motivo fondamentale per cui voglio loggare molte cose)
potrebbe non venir documentato per tempo.
Per ora mi hanno suggerito la condivisione di un descrittore di file
per tutti i thread, e l'uso della write() che, a livello di OS,
dovrebbe consentire l'atomicità nelle scritture.
Proverò e vi terrò aggiornati :)

 consentire l'atomicità nelle scritture. Proverò e vi terrò aggiornati :)
Si`, purtroppo l'avevo letto dopo aver postato il mio messaggio.
Potrebbe essere la soluzione migliore, ma occhio agli interrupt, 
soprattutto sui multicore.
Bye.

  
Il problema non è la scrittura su socket in se.
Il problema è se il demone syslogd riesce a stare dietro a tutti i 
messaggi che gli mandi.
Ad esempio se usi UDP con un socket di tipo UNIX, puoi impostare il 
socket in modalità non bloccante.
Ma se il server ha il buffer pieno, la send fallisce.
Potresti inviare i messaggi di log ad N server diversi.
Ma secondo me questo è un tipico caso di ottimizzazione preventiva.
Ciao  Manlio

 una scrittura su socket.
Sicuramente è più rapida la write() della send() + write().
Però è anche vero che se la write() da sola non è affidabile (perchè
la scrittura non è _immediata_ e quindi un crash non verrebbe
documentato), se la write() necessita di un fsync e quindi di
atomicizzare le due operazioni e quindi gestire le concorrenze, allora
probabilmente sarebbe meglio un processo esterno infinitamente meno
complesso di quello fondamentale e quindi con meno probabilità di
crash.

 dei varii threads. Qualcosa del tipo:
Aggiungi un thread logger in listen su una porta UDP bindata locale e 
modifica l'addlog in modo che mandi un UDP invece che scrivere sul file.
In questo modo hai la certezza che i tuoi thread non vengano mai fermati 
dal logging, che e` una certezza importante, perche` e' molto + facile 
perdere tempo tra fopen/fwrite/fclose che non nel lock/unlock di un 
semaforo!
--
Bye,
  Gabry