Newsgroup: it.comp.programmare.win32
Titolo: variabili condivise tra thread

salve,
devo condividere alcune variabili tra due thread di un processo, in visual 
C.
Ho visto che anche se un thread legge solamente e un altro scrive solamente
sulla stessa variabile ci possono essere problemi.
Ho risolto usando le critical section ma mi sembra troppo macchinoso per
settare e leggere una semplice variabile
(initialize, begin, end critical section da ambe le parti, per una semplice 
a = 1; )
Domanda: esiste un modo rapido per settare/leggere una variabile condivisa 
tra thread
senza avere sporadicamente degli effetti indesiderati di clash 
lettura/scrittura?
grazie
GCL

 lettura/scrittura?
La risposta breve? No.
-- 
Lawrence - 
http://www.oluyede.org/blog
"Nothing is more dangerous than an idea
if it's the only one you have" - E. A. Chartier

> La risposta breve? No.
sigh! grazie, anche se speravo in una risposta diversa! :-)
solo una cosa (non per insistere ma per capire...):
avevo sentito "rumors" di una istruzione che permetteva
una assegnazione protetta di varibile atomica condivisa tra due thread,
francamente non so se esista o se ho capito male,
esiste un tutorial piu' "umano" dell'MSDN sul multi-threading?
bye
GCL

 lettura/scrittura?
Se dovessi fare solo incrementi e decrementi, allora esistono 
InterlockedIncrement e InterlockedDecrement che ti semplificano la vita.
-- 
Raffaele Rialdi
Microsoft .NET MVP 
http://mvp.support.microsoft.com
 -
http://italy.mvps.org
 UGIdotNET - User Group Italiano .NET
http://www.ugidotnet.org
 Weblog: 
http://blogs.ugidotnet.org/raffaele

 una assegnazione protetta di varibile atomica condivisa tra due thread,
forse InterlockedExchange può fare al caso tuo
-- 
Pela

> forse InterlockedExchange può fare al caso tuo
ottimo, era quello che mi serviva
grazie 1000
GCL 

Se dovessi fare solo incrementi e decrementi, 
 dovrebbero essere gia 'atomiche' in quanto avvengono in un unico
ciclo di clock (almeno sui proessori a 32 bit)

 ciclo di clock (almeno sui proessori a 32 bit)
č vero che č un'operazione atomica ma č impossibile che venga portata a
termine in un solo ciclo: si sta parlando di variabili in memoria, non di
registri.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 sulla stessa variabile ci possono essere problemi.
questo non è vero: la lettura o la scrittura su una variabile di
dimensione <= 4 byte è atomica su processori a 32 bit.
hai necessità di sincronizzare solamente se esiste la possibilità che lo
stato complessivo di più variabili sia inconsistente in seguito ad una
assegnazione (in altre parole, devi sincronizzare solo se almeno uno dei
tuoi thread deve effettuare due o più operazioni atomiche in maniera
atomica).
esempio pratico: hai due variabili ULONG (per dire), a e b; senza
nessun'altra condizione imposta sulle due variabili le puoi usare
"inter-thread" in tutta tranquillità senza disturbare nessun meccanismo di
sincronizzazione.
introduciamo una condizione: a può essere nulla solo se b è uguale a 5.
ora un thread vuole eseguire questo codice:
b = 6;
mentre un altro vuole eseguire questo:
if (b == 5) {
    a = 0;
}
la cosa potrebbe causare uno stato inconsistente:
inizialmente b == 5
thread 2 esegue if (b == 5)
thread 1 esegue b = 6;
thread 2 esegue a = 0;
alla fine b vale 6 e a vale 0, contro la condizione imposta, perciò le
variabili vanno sincronizzate; con un piccolo hack si potrebbe evitare di
usare una critical section e limitarsi a cambiare il codice del secondo
thread come segue:
a = b;
InterlockedCompareExchange(&a, 0, 5);


 dimensione <= 4 byte è atomica su processori a 32 bit.
mmm...
due domande:
1) se cio' e ' vero a cosa serve la InterlockedIncrement()?  basterebbe fare 
variabile++;
2) tutto questo vale anche per windows CE 5.0? e' li' che ho avuto 
problemi...

 problemi...
Non vorrei sbagliarmi ma gli ARM hanno una architettura rigorosamente 
RISC il che significa che un incremento viene diviso in: caricamento 
della variabile, incremento del registro e scrittura della variabile. Va 
da se' che una simile operazione non e' atomica.
Su pc, invece, ricordati che una variabile ANCHE ATOMICA condivisa tra 
piu' thread andrebbe dichiarata "volatile" per evitare possibili casini 
con la cache.

 con la cache.
scusa l'ignoranza, potresti spiegarmi cosa fa la dichiarazione volatile?
evita il caricamento in cache? altrimenti un thread la aggiorna e l'altro la 
vede ancora "vecchia"?
GCL 

 che una simile operazione non e' atomica.
effettivamente il problema lo ho avuto su una macchina con winCE e con non 
un Pentium ma un VIA
come microprocessore, per cui forse anche il VIA spezza le istruzioni. Fatto 
sta che una
assegnazione senza critical section veniva a volte (rarissime) cannata... 

 
Non è detto che sia un'operazione atomica e non interrompibile.
Anzi, su alcuni processori RISC (ARM, ad esempio), non è _mai_ 
un'operazione atomica, visto che il dato deve essere letto dalla 
memoria, modificato in un registro e poi riscritto.
Tutte le variabili condivise tra thread devono essere "protette" con un 
meccanismo di sincronizzazzione per evitare problemi molto rognosi e 
molto complessi da debuggare.
-- 
Valter Minute
(the reply address of this message is invalid)
(l'indirizzo di reply di questo messaggio non è valido)

 per evitare possibili casini con la cache.
Non solo sul PC. Anche su Windows CE e in generale sui sistemi 
operativi multithreading e ogni volta che si accede a dati che 
possono essere modificati al di fuori del flusso di esecuzione di una 
singola funzione (ex: quando leggi registri hardware).
"volatile" non evita problemi con la cache, ma fa in modo che il 
compilatore generi codice che rilegga esplicitamente il dato dalla 
memoria (magari pure passando per la cache, che è coerente in lettura 
/scrittura).
Il rischio è che il compilatore carichi il valore della variabile in 
un registro e non lo rilegga a ogni accesso. Questo è un vantaggio in 
termini di performance (gli accessi in memoria sono sempre più un 
collo di bottiglia per le CPU), ma può avere effetti devastanti su 
variabili modificate da thread diversi (cosa che il compilatore non 
può, ovviamente, sapere a priori).


 dimensione <= 4 byte è atomica su processori a 32 bit.
Quello che dici non è vero (vedi le altre mie risposte in questo 
thread) e darlo per scontato è molto pericoloso.


 lettura/scrittura?
Se si tratta di un valore a 32 bit ci sono le API 
InterlockedIncrement/Decrement/Exchange.


 vede ancora "vecchia"?
Esatto. Solo non per effetto della cache, ma per le ottimizzazioni del 
compilatore.

 sulla stessa variabile ci possono essere problemi.
Sono d'accordo con tutti quelli che hanno risposto di utilizzare le 
critical sections.
Sono anche d'accordo con chi sconsiglia di fidarsi dei "dettagli" 
hardware della cpu.
Tuttavia in questo caso MOLTO specifico:
1 - Se UNO ed UN SOLO thread scrive
2 - Se ho N threads che leggono
3 - Se stiamo parlando di una variabile "nativa" tipo int
Allora se TUTTE le 3 condizioni sono verificate, non vedo che problemi 
possano saltar fuori lasciando "sprotetto" l'accesso alla variabile, con 
  l'ovvia accortezza di dichiararla volatile.
Io ho usato spesso questo schema e non ho mai avuto nessun problema.
Anzi se tu, o qualcun altro vede problemi, lo faccia sapere e parliamone.
Ciao

 
Specifico meglio:
In realtà per essere veramente al sicuro, la variabile in questione 
dovrebbe anche essere allineata correttamente, e questa condizione 
potrebbe non essere banale da rispettare!

 parliamone. 
Io problemi che vedo sono soprattutto di manutenibilità e portabilità 
del codice.
Se lo porti su un microprocessore con lunghezza della word diversa è 
possibile che il codice inizi a dare problemi (possono servire più 
scritture per cambiare il valore della variabile, oppure il dato 
viene "mascherato" in fase di lettura/scrittura) e una modifica 
all'algoritmo che comporti, magari, scritture da thread diversi ti 
può portare ad avere problemi di sincronizzazione, magari sparsi in 
giro per il codice (perchè la variabile la usi normalmente e non 
tramite una chiamata come probabilmente avresti fatto usando le 
critical section).
Ripeto che, secondo me, il gioco non vale la candela. Qualche linea 
di codice in meno di codice da scrivere o qualche tick di clock 
risparmiato non compensano i potenziali (e rognosissimi) problemi che  
rischi di trovarti in futuro.
Inoltre abituandoti ad usare _sempre_ la sincronizzazione non rischi 
di sbagliare.


  l'ovvia accortezza di dichiararla volatile.
E' un approccio che funziona bene a patto che il processore su cui 
lavori abbia la word grande quanto un int (cioe' tutti esclusi i 
microcontrollori di fascia bassa).

 thread) e darlo per scontato è molto pericoloso.
lettura e scrittura di un numero di byte che non supera la dimensione del
bus di dati sono atomiche di per se' su qualsiasi processore, RISC o CISC,
non lo sono *assieme*. se sono su un RISC a 32 bit e devo leggere 4 byte
posso farlo tranquillamente; se devo scriverli anche; se devo
incrementarli invece no (perché dovrei leggerli, modificarli e scriverli).
l'unico problema è quando non si tratta ne' di 1 byte, ne' di 2 ne' di 4,
ma di 3; a parte che il C (come qualsiasi altro linguaggio) penso non
abbia nessuna implementazione con tipi nativi da 3 byte, e a parte che
comunque sia per la lettura non c'è problema neanche in questo ipotetico
caso, ma scrivere 3 bytes potrebbe essere un'operazione che io sono
costretto ad effettuare in maniera non atomica. però se io sono a
conoscenza del quarto byte (cioè quello che segue immediatamente i 3 di
questa ipotetica variabile) posso effettuare una scrittura da 4 byte in
maniera atomica.


 problemi...
boh ^^
non so quasi nulla di Windows CE, sempre lavorato sui classici "Wintel" :)


 il processore.
volendo interpretare quello che hai detto si potrebbe precisare che
volatile dice al compilatore di non "cachare" le variabili nei registri :D


 atomica. 
Con alcuni processori è un problema anche a 2 e a 1 se lo short o 
byte non è allineato a 32 bit.
E siamo sicuri che non uscirà un 64bit con gli stessi problemi nei 
riguardi dei dati a 32bit?
Sto facendo l'avvocato del diavolo, però voglio farti capire che per 
fregarsene di una povera critical section si rischia di dover mettere 
tantissimi "paletti" e di rischiare che quel codice, per qualche 
motivo, un domani possa non funzionare più cambiando processore, 
compilatore o parametri di compilazione.


 compilatore o parametri di compilazione.
suvvia, fregarsene di una critical section significa due possibili cose:
1) usare InterlockedXxx, che altro non sono che istruzioni assembly col
prefisso LOCK (e solo per i multicore, sui processori singoli non hanno
neanche il prefisso)
2) leggere *O* scrivere una variabile che però devo controllare che sia
allineata ai 32/64 bit
insomma, più che scomodare una critical section io andrei a controllare
che la variabile sia allineata :D
per totale onestà aggiungo che quest'ultima cosa fino ad oggi non l'avrei
fatta perché non sapevo che esistesse il problema relativo
all'allineamento (che poi perché esiste?? al bus degli indirizzi mancano i
due bit meno significativi??), ma con tutta probabilità me la sarei cavata
in maniera indolore :P


 me la sarei cavata in maniera indolore :P
Il problema non è cavarsela in maniera indolore quasi sempre, è 
evitare problemi dolorosissimi una tantum! :)


 sempre la critical section o le funzioni Interlocked*.
ma se sai che stai compilando per un'architettura Intel (che quando si ha
a che fare con Windows è il 99% abbondante delle situazioni) usare le
critical sections per sincronizzare singole variabili è TOTALMENTE
inutile; ciò che non puoi dare per scontato è che il programma girerà su
un'architettura a core singolo, di conseguenza quello che non devi evitare
sono le InterlockedXxx.


 Windows "non-CE" gira solo su PC;
Pensandoci non e' tanto vero: windoze gira anche su IA64 che come 
architettura e' ben piu' strana di quella ARM :)

 conseguenza quello che non devi evitare sono le InterlockedXxx.
Non dico di usare le critical section per tutte le variabili, ci 
mancherebbe! Solo per quelle che devono essere usate da thread diversi.
L'1% dei casi è un enormità, in termini informatici! Se si piantassero 
l'1% dei sistemi a microprocessore torneremmo all'età della pietra.
L'uso della critical section nel caso del nostro amico è, IMVHO, 
assolutamente indispensabile, a meno che le variabili in questione 
vengano aggiornate e rilette ogni microsecondo. 
In quel caso, probebilmente ripenserei l'idea di usare due thread (e 
magari cercherei un S.O. che mi garantisce tempi di risposta più certi 
di quelli di XP...).
Se ti devi preoccupare dell'overhead di una critical section (prova a 
vedere quanto pesa accederci quando non è in uso... su Windows CE c'è 
un tool che si chiama OSBench, non so se ci sono cose simili per Win32) 
probabilmente ti devi preoccupare anche del fatto che il tuo sistema 
lavori sempre alla stessa temperatura e utilizzi componenti con 
tolleranze minime.
E' una questione filosofica. C'è chi preferisce scrivere e debuggare il 
codice una sola volta nella vita!


 io sappia Windows "non-CE" gira solo su PC; 
Giusto una nota.
Il sistema operativo della XBox, così come quello della nuova XBox 360 
è basato su windows 2000.
L'XBox 360 usa un microprocessore diverso (powerPC) con architettura e 
addirittura endianness differenti. Tempo fa avevo letto un blog che 
parlava del porting di una versione del runtime .NET su 
quell'architettura e ci si scontrava proprio su questo tipo di 
problemi.
Windows NT girava anche su MIPS e alpha, Windows Vista dovrebbe girare 
anche sulle architetture a 64 bit e non è da escludere che in futuro 
possano emergere architetture diverse (PPC?).
Pensa a chi programmava sotto MacOSX dando per scontato che il suo 
codice sarebbe stato compilato per un processore PowerPC!
