Riassumiamo:
Se costruisco una funzione che usa variabili globali (non rientrante), e il codice di questa funzione lo racchiudo fra l'acquisizione e il rilascio di un semaforo, rendo la funzione thread-safe, in quanto costruirò le altre funzioni che operano sulla stessa variabile globale (che saranno chiamate in altri thread) con lo stesso principio, cioè per essere eseguite dovranno acquisire lo stesso semaforo della funzione di prima. Però, se non faccio in questo modo (cioè in una di queste funzioni che operano su quella variabile globale non uso i meccanismi di lock), posso modificare la variabile globale nonostante abbia reso thread-safe quelle funzioni, dato che quest'ultima funzione senza meccanismi di lock può intervenire in qualsiasi momento.
Perfetto!
Ora, vale lo stesso con le funzioni thread-safe di libreria? L'essere thread-safe mette al riparo da tutto oppure, per i motivi di cui sopra, ci sono comunque i rischi legati alla non rientranza di queste funzioni?
L'essere thread safe mette dal riparo da tutto.
Ma attenzione: questo finche' non vai a
pasticciare con le strutture dati
interne utilizzate da queste funzioni. Ma questo non puo' essere fatto, se ti attieni all'uso normale della libreria.
Lo puoi fare usando sistemi trucchettosi: reflection (java, C#), oppure mediante l'analisi del codice binario. Ma se sai fare queste cose, conosci anche le problematiche del multithreading
Se devi implementare delle funzioni thread-safe, quello che fai e' fare in modo che
nessuno a parte le tue funzioni, possa accedere alle strutture dati di servizio, proprio per avere un controllo totale, e quindi coordinarne l'accesso, a tali informazioni.
Un'altra cosa: per quanto riguarda la non rientranza nei signal handler (non in concorrenza quindi), secondo me non è risolvibile coi semafori, dato che un segnale può interrmpere in qualunque momento: l'unica soluzione è usare funzioni rientranti nei signal handler. O no?
Non e' vero: il semaforo assicura che il signal handler venga eseguito da un thread alla volta.
La situazione
limite e' quando all'interno dell'implementazione del signal handler, viene richiamato lo stesso signal handler!
Ma compito del programmatore e' assicurarsi che questo non possa mai succedere (generalmente mediante un'opportuna implementazione, oppure mediante dei flag).
Una funzione e' rientrante se l'essere eseguita da piu' thread contemporanemante non genera comportamenti strani. Quindi usa solo variabili locali, allocate sullo stack del thread corrente, o nello heap al momento della chiamata, ed eventualmente chiama altre funzioni rientranti o thread-safe
Una funzione rientrante e' ovviamente/naturalmente thread-safe
Non tutte le funzioni sono thread-safe, pero'.
Perche' non si implementano tutte le funzioni in modo da essere thread-safe?
Il problema e' l'efficienza: assicurare che una funzione sia thread-safe puo' ridurre le performance della funziona anche di un fattore 1000. E nel 99% dei casi non serve, nel senso che chi progetta il softwrae puo' identificare i soli punti in cui serve effettivamente una sincronizzazione.
Ed in un programma anche complesso, questi punti sono generalmente molto pochi: quindi e' sufficiente che il programmatore si assicuri il controllo della concorrenza solo in questi punti.
Alcune funzioni
devono necessariamente essere thread safe, comunque: come minimo
1) l'allocazione della memoria
2) la creazione dei thread
3) l'accesso ad altre risorse gestite direttamente dal sistema operativo (file, tastiera, monitor, ...)
questo perche' il loro controllo e' al di fuori delle possibilita' del programma in cui vengono eseguite, oltre la fatto che dimenticarsene sarebbe disastroso. Quindi poiche' deve essere sempre fatto, tanto vale che lo facciano direttamente loro.