Newsgroup: it.comp.lang.c++
Titolo: Multithreading e interi

Scusate se il problema è già stato posto ma non sono riuscito a
trovare una risposta convincente, anzi ne ho trovate tante,
discordanti e variegate.
Il problema è:
E' thread-safe o no leggere e scrivere una variabile a 32 bit
(integer, boolean, DWORD, ecc.) senza mettere CriticalSection o altri
oggetti per il controllo della concorrenza?
esempio:
main
  int myID = 0;
thread1:
   while(1)
   {
      myID = func()
   }
thread2:
  while(1)
  {
    if(myID == 0)
    {
       DoSomething();
    }
  }
In teoria il compilatore dovrebbe compilare l'operazione con una
singola, atomica operazione del (o di uno dei) processori.
ma ci sono opinioni discordanti....
qualcuno ha esperienze in merito? in particolare su C++ Builder &
MSVC++6?
Grazie a tutti in anticipo,
Maurizio Baldi

   }
non e' thread safe:
poni che nel th1 func metta a 0 la variabile e al ciclo subito dopo la metta
ad un valore diverso da 0. Se in questo lasso di tempo il th2 non fosse
stato in esecuzione, per motivi di scheduling indipendenti dal tuo
programma, DoSomething non sarebbe mai stata invocata quindi non sarebbe
successo quello che ti eri aspettato facesse il tuo programma.
una soluzione potrebbe essere:
th1:
    while(1)
    {
       wait(EventoConsumato)
       myID = func();
       signal(EventoProdotto)
    }
th2:
    while(1)
    {
        wait(EventoProdotto)
        if(myID == 0)
        {
            DoSomething();
        }
        signal(EventoConsumato)
    }
dove wait e signal sono le chiamate per operare sui semafori (in questo caso
EventoProdotto ed EventoConsumato) che ti da il tui sistema operativo.
Ovviamente i semafori devono essere inizializzati affinche' alla prima
iterazione EventoConsumato sia "aperto" ed EventoProdotto sia "chiuso". In
questa maniera arbitri il programma e fai in modo che succeda cio' che ti
aspettavi.
ciao,
Nids
ps: per quanto riguarda i semafori di windows cerca su google e su msdn

 aspettavi.
Scusa l'intromissione.
A questo punto visto che di C++ si tratta consiglierei all'OP anche di 
guardarsi i pattern "Lock" e "Guard" di ACE.
Cerca con google questo articolo: "Strategized Locking. Thread-safe 
Interface, and Scoped Locking" di Douglas C. Schimidt.
In questo modo si evita di usare l'accoppiata di funzioni wait e signal 
e si usa invece una Guard che protegge tutto lo scope di una funzione 
senza le doppie chiamate.
Lock e' una classe wrapper attorno alle funzioni "wait" e "signal".
Di solito si usa con un mutex. L'oggetto lock deve essere visibile alle 
2 funzioni.
lock.acquire() aquisisce un mutex, lock.release() lo rilascia.
La guardia e' una classe che prende un riferimento al lock, ne chiama la 
acquire e nel suo distruttore chiama lock.release().
Con questo trucco si sincronizza tutto uno scope.
In questo modo il codice diventa:
Lock lock_;
  th1:
      while(1)
      {
         Guard guard_(&lock_); // supponendo Guard::Guard(Lock*)
         myID = func();
         // fine scope protetto
      }
  th2:
      while(1)
      {
          Guard guard_(&lock_);
          if(myID == 0)
          {
              DoSomething();
          }
          // fine scope protetto
      }


Vi ringrazio molto.... ma in realtà la cosa che volevo sapere è molto più
banale....
nel mio esempio (fuorviante, a dire il vero) non volevo sincronizzare i
thread ma solo capire se potevo incorrere in un access violation...
Ho un access violation in un mio programma multithreading che viene fuori
con cadenza circa settimanale e vorrei togliermi definitivamente il dubbio
che possa dipendere dagli accessi concorrenti sugli interi...
ciao e grazie,
Maurizio

       }
Cosi' eviti che th2 legga mentre th1 scrive pero' non risolvi il problema
della segnalazione dell'evento prodotto e dell'evento consumato, quello nel
quale il valore di ,myID veniva cambiato due volte di seguito da th1 e th2
non se ne accorgeva in quanto deshedulato.
Io non conosco ACE, i thread ed i semafori gli ho visti in C (con posix e
sysV) ed in java, la cosa che intendo io e' una "condition variable" che si
testa prima di poter eseguire un dato pezzo di codice al fine di assicurarsi
che la variabile contenga il valore giusto, come nel piu' banale pattern
produttore consumatore reperibile su ogni libro di sistemi
operativi/programmazione multithread.
seguendo (a naso) l'esempio del lock:
lock Prodotto;
lock Consumato;
//qui inizializzi i due lock in maniera appropriata:
//Consumato = "aperto"
//Prodotto = "chiuso"
  th1:
      while(1)
      {
         Consumato.aquire();
         myID = func();
         Prodotto.release();
      }
  th2:
      while(1)
      {
          Prodotto.acquire();
          if(myID == 0)
          {
              DoSomething();
          }
          Consumato.release();
      }
ciao,
Nids
ps: quanto sarebbero belli i thread c++ ansi! :)
pps: quanto sarebbero belli i socket c++ ansi! :)

 non se ne accorgeva in quanto deshedulato.
E si hai ragione.
Anche se le condition variable hanno associato un mutex.
Una soluzione "C++ oriented" al problema dovrebbe essere
int myID;
Lock lock_; // wrapper OS mutex
Condition cond_; // wrapper OS cond var
while(1)
     {
        Guard guard_(&lock_); // Guard::Guard(Lock* l) {l->acquire();}
        if (myID = func() == 0)
           cond_.signal();
        // Guard::~Guard() {l->release();}
     }
th2:
     while(1)
     {
         Guard guard_(&lock_);
         cond_.wait(lock_.get()); // get ritorna il reference al mutex 
OS correlato alla condition variable
         if(myID == 0)
             DoSomething();
     }
ma va' che bello! Neanche una doppia chiamata alla new/delete.
Ciao,
Cesare

 che possa dipendere dagli accessi concorrenti sugli interi...
E' molto probabile che tu abbia un problema di concorrenza.
Purtroppo l'unica tecnica che ho scoperto per beccare questi heisenbug 
e' avere fortuna.
Anzi sarebbe bello che qualcuno possa un giorno dirmi come fare a 
risolvere questi bug subdoli.
Ciao,
Cesare

 interi...
In C e in C++ esiste la keyword 'volatile' che ha il preciso scopo
di disabilitare ottimizzazioni che potrebbero rendere non thread-safe
l'accesso alle variabili.
Il tuo esempio funziona solo se le ottimizzazioni sono disattivate.
Compilando con un compilatore ottimizzante, 'thread1' scrive il
risultato di 'func' in un registro, mentre 'thread2' verifica se un
altro registro e' ancora a zero.
Tutte le variabili condivise da piu' thread dovrebbero avere la storage
class 'volatile', a meno che la register allocation delle variabili sia
comunque disabilitata.
Per quanto riguarda l'access violation con cadenza settimanale, penso
che non c'entrino i thread, perche' l'errore non avrebbe una
tale regolarita'. Potrebbe trattarsi di un contatore che continua a
crescere e diventa negativo, o di un puntatore o indice in un array che
continua a incrementarsi e supera il limite dell'array.
-- 
	Carlo Milanesi
 
http://digilander.libero.it/carlmila

 ha scritto nel messaggio
news:20031122195043.1cf74e69...@libero.it...

Non so che dire.......... grazie mille... hai risolto il mio dilemma.... e
quasi sicuramente hai trovato il bug, visto che c'č da qualche parte un
if(iNumLoop++ == 0)
{
    la prima volta fai cosě
}
else
{
    altrimenti cosě
}
quindi potrebbe davvero trattarsi di un overflow...
ehhhhh a volte lo odio questo bellissimo mestiere ;-)
Saluti,
Maurizio