Singleton ThreadSafe

di il
3 risposte

Singleton ThreadSafe

Buongiorno a tutti 

premessa: 

per motivi di lavoro (che non sto' quì a spiegare), sono rimasto un bel po' indietro con lo standard c++ ,ho sempre usato pthread_mutex (POSIX), non vorrei che uso in modo inappropriato std::unique_lock e/o std::lock_guard.

per agevolare la lettura del file out_helgrind_approx.txt nel main.cpp ho stampato l'indirizzo di memoria di : myclass::idata e singleton::myInstance

Risultato : Compilando e lasciando invariato il main.cpp, valgrind mi avvisa che ho dei data-race-detection sulla variabile myclass::idata.

Quello che non riesco a capire :

  1. Perchè se uso il mutex std::unique_lock  dentro "void myclass::increment()" valgrind mi dice che è tutto ok e non mi avvisa della data-race-condition (tag: #01?) ?
  2. Uso in modo corretto il mutex std::unique_lock dentro getInstance() ?
  3. Uso in modo corretto valgrind per capire i data-race-detection ?
  4. Perchè se uso getInstance(), myInstance dichiarato static dentro la funzione , (tag #a3) ho sempre il data-race-detection anche se uso il mutex std::unique_lock dentro increment() ?
  5. Ho trovato in molti esempi con il doppio check del puntatore myInstancePtr come mostrato dentro la funzione getInstancePtr() (tag: #a1b), non riesco a capire perchè l'if è prima del mutex (tag: #02?). Il mutex ha lo scopo di proteggere le risorse condivise. 

    // ho sempre usato i mutex in questo modo
    pthread_mutex_lock
       if ( !puntatore_condiviso) 
       ...
       ...
    pthread_mutex_unlock   

Spero di essere stato chiaro.

Vi ringrazio in anticipo.

opzione di compilazione : -O3 -g3 -pedantic -Wall -Wextra -g -ggdb -pthread

comando esecuzione valgrind

$ valgrind --tool=helgrind --history-level=approx --log-file=out_helgrind_approx.txt -s ./singleton
 --->>> costructor my class <<<--- 
 address idata : 0x602248
start singleton ...
 address instance : 0x602240
 num. of threads : 10
 idata : 1000
... done!
 --->>> destructor my class <<<---


main.cpp :

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>

namespace ns_singleton {
    template<typename T>
	class singleton {
	private:
		singleton() { std::cout << "\t --->>> singleton() <<<--- \n"; }
 		virtual ~singleton() { std::cout << "\t --->>> ~singleton() <<<--- \n"; }
		singleton(const singleton &other) = delete; // Prevent copy constructor
		singleton(singleton &&other) = delete; // Prevent move constructor
		singleton& operator=(const singleton&) = delete; // Prevent copy assignment
//#a1   static T* myInstancePtr;
		static std::mutex mtx;
        static T myInstance;

    public:
            
//#a1   static T* getInstancePtr() {
//#a1       std::unique_lock<std::mutex> lock(mtx);
//#a1       if( !myInstancePtr )
//#a1          myInstancePtr = new T();
//#a1       return myInstancePtr;
//#a1   }

//#a1   static void delInstace() { 
//#a1       std::unique_lock<std::mutex> lock(mtx);
//#a1       if( myInstancePtr )
//#a1           delete myInstancePtr; 
//#a1   }

//#a1B  singleton* singleton::getInstancePtr() {
//#a1B      if (!myInstancePtr) { // #02? perchè l'if è prima del mutex ?
//#a1B        	std::unique_lock<std::mutex> lock(mtx); // acquire lock
//#a1B      	if (!myInstancePtr) { // double-checked locking
//#a1B      		myInstancePtr = new singleton();
//#a1B      	}
//#a1B      	lock.unlock(); // release lock
//#a1B      }
//#a1B      return myInstancePtr;
//#a1B  }
            
//#a3   static T& getInstance() { // data race detection sempre anche se uso il mutex dentro "increment()"
//#a3       static T myInstance;
//#a3       std::unique_lock<std::mutex> lock(singleton::mtx);
//#a3       //std::lock_guard<std::mutex> lock( singleton::mtx );
//#a3       return myInstance;
//#a3   }

        static T& getInstance() {
            std::unique_lock<std::mutex> lock(singleton::mtx);
            //std::lock_guard<std::mutex> lock(mtx);
            return myInstance;
        }

	};

    //#a1  
    //template<typename T> 
    //T* singleton<T>::myInstancePtr = nullptr;
    
    template<typename T> 
    T singleton<T>::myInstance;
	
    template<typename T> 
    std::mutex singleton<T>::mtx;

} /* namespace n_singleton */

class myclass {
    public:
        myclass() : idata(0) { std::cout << " --->>> costructor my class <<<--- \n address idata : "<< std::hex << &idata << std::dec << "\n"; } 
        virtual ~myclass() { std::cout << " --->>> destructor my class <<<--- \n"; }
        void increment() { 
            //std::unique_lock<std::mutex> lock(mtx_idata); // #01? no data race detection c'è già un mutex dentro "getInstance()", why is it work ?
            ++idata; 
        }
        void display_data() { std::cout << " idata : " << idata << "\n"; }
    private:
        int idata = 0;
        std::mutex mtx_idata;
};

void thread_func() {
    for( int cnt = 0; cnt < 100; cnt++) {
        //#a1 ns_singleton::singleton<myclass>::getInstancePtr()->increment();
        ns_singleton::singleton<myclass>::getInstance().increment();
    }
}

int main() {
	std::cout << "start singleton ...\n";
	std::vector<std::thread> vth; 
	std::cout << " address instance : " << &ns_singleton::singleton<myclass>::getInstance() << "\n";
    for( int i = 0; i < 10; i++) {
        std::thread th1( thread_func );
        vth.emplace_back(std::move(th1));
    }	
	std::cout << " num. of threads : " << vth.size() << "\n";
    for(auto & elem : vth) 
        elem.join();
    ns_singleton::singleton<myclass>::getInstance().display_data();
    //#a1 ns_singleton::singleton<myclass>::delInstace();
	std::cout << "... done!\n";
	return 0;
}

out_helgrind_approx senza mutex dentro myclass::incremente() :

==9400== Helgrind, a thread error detector
==9400== Copyright (C) 2007-2024, and GNU GPL'd, by OpenWorks LLP et al.
==9400== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
==9400== Command: ./singleton
==9400== Parent PID: 5721
==9400== 
==9400== ---Thread-Announcement------------------------------------------
==9400== 
==9400== Thread #3 was created
==9400==    at 0x5A99122: clone (in /usr/lib64/libc-2.28.so)
==9400==    by 0x57840CE: create_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5785B55: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x4C40F0C: pthread_create_WRK (hg_intercepts.c:445)
==9400==    by 0x4C427FA: pthread_create@* (hg_intercepts.c:478)
==9400==    by 0x4F10E08: std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x400FE4: thread<void (&)()> (thread:131)
==9400==    by 0x400FE4: main (main.cpp:110)
==9400== 
==9400== ---Thread-Announcement------------------------------------------
==9400== 
==9400== Thread #2 was created
==9400==    at 0x5A99122: clone (in /usr/lib64/libc-2.28.so)
==9400==    by 0x57840CE: create_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5785B55: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x4C40F0C: pthread_create_WRK (hg_intercepts.c:445)
==9400==    by 0x4C427FA: pthread_create@* (hg_intercepts.c:478)
==9400==    by 0x4F10E08: std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x400FE4: thread<void (&)()> (thread:131)
==9400==    by 0x400FE4: main (main.cpp:110)
==9400== 
==9400== ----------------------------------------------------------------
==9400== 
==9400== Possible data race during read of size 4 at 0x602248 by thread #3
==9400== Locks held: none
==9400==    at 0x4012E8: increment (main.cpp:90)
==9400==    by 0x4012E8: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  This conflicts with a previous access by thread #2, after
==9400==    at 0x4C3E168: mutex_unlock_WRK (hg_intercepts.c:1181)
==9400==    by 0x4C42C0D: pthread_mutex_unlock (hg_intercepts.c:1202)
==9400==    by 0x4012E7: __gthread_mutex_unlock (gthr-default.h:778)
==9400==    by 0x4012E7: unlock (std_mutex.h:121)
==9400==    by 0x4012E7: unlock (std_mutex.h:323)
==9400==    by 0x4012E7: ~unique_lock (std_mutex.h:232)
==9400==    by 0x4012E7: getInstance (main.cpp:65)
==9400==    by 0x4012E7: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  but before
==9400==    (the end of the thread)
==9400==  Address 0x602248 is in the BSS segment of /home/developer/mytmp/singleton/build/singleton
==9400== 
==9400== ----------------------------------------------------------------
==9400== 
==9400== Possible data race during write of size 4 at 0x602248 by thread #3
==9400== Locks held: none
==9400==    at 0x4012E8: increment (main.cpp:90)
==9400==    by 0x4012E8: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  This conflicts with a previous access by thread #2, after
==9400==    at 0x4C3E168: mutex_unlock_WRK (hg_intercepts.c:1181)
==9400==    by 0x4C42C0D: pthread_mutex_unlock (hg_intercepts.c:1202)
==9400==    by 0x4012E7: __gthread_mutex_unlock (gthr-default.h:778)
==9400==    by 0x4012E7: unlock (std_mutex.h:121)
==9400==    by 0x4012E7: unlock (std_mutex.h:323)
==9400==    by 0x4012E7: ~unique_lock (std_mutex.h:232)
==9400==    by 0x4012E7: getInstance (main.cpp:65)
==9400==    by 0x4012E7: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  but before
==9400==    (the end of the thread)
==9400==  Address 0x602248 is in the BSS segment of /home/developer/mytmp/singleton/build/singleton
==9400== 
==9400== 
==9400== ERROR SUMMARY: 1800 errors from 2 contexts (suppressed: 2736 from 7)
==9400== 
==9400== 900 errors in context 1 of 2:
==9400== ----------------------------------------------------------------
==9400== 
==9400== Possible data race during write of size 4 at 0x602248 by thread #3
==9400== Locks held: none
==9400==    at 0x4012E8: increment (main.cpp:90)
==9400==    by 0x4012E8: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  This conflicts with a previous access by thread #2, after
==9400==    at 0x4C3E168: mutex_unlock_WRK (hg_intercepts.c:1181)
==9400==    by 0x4C42C0D: pthread_mutex_unlock (hg_intercepts.c:1202)
==9400==    by 0x4012E7: __gthread_mutex_unlock (gthr-default.h:778)
==9400==    by 0x4012E7: unlock (std_mutex.h:121)
==9400==    by 0x4012E7: unlock (std_mutex.h:323)
==9400==    by 0x4012E7: ~unique_lock (std_mutex.h:232)
==9400==    by 0x4012E7: getInstance (main.cpp:65)
==9400==    by 0x4012E7: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  but before
==9400==    (the end of the thread)
==9400==  Address 0x602248 is in the BSS segment of /home/developer/mytmp/singleton/build/singleton
==9400== 
==9400== 
==9400== 900 errors in context 2 of 2:
==9400== ----------------------------------------------------------------
==9400== 
==9400== Possible data race during read of size 4 at 0x602248 by thread #3
==9400== Locks held: none
==9400==    at 0x4012E8: increment (main.cpp:90)
==9400==    by 0x4012E8: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  This conflicts with a previous access by thread #2, after
==9400==    at 0x4C3E168: mutex_unlock_WRK (hg_intercepts.c:1181)
==9400==    by 0x4C42C0D: pthread_mutex_unlock (hg_intercepts.c:1202)
==9400==    by 0x4012E7: __gthread_mutex_unlock (gthr-default.h:778)
==9400==    by 0x4012E7: unlock (std_mutex.h:121)
==9400==    by 0x4012E7: unlock (std_mutex.h:323)
==9400==    by 0x4012E7: ~unique_lock (std_mutex.h:232)
==9400==    by 0x4012E7: getInstance (main.cpp:65)
==9400==    by 0x4012E7: thread_func() (main.cpp:101)
==9400==    by 0x4F10B22: ??? (in /usr/lib64/libstdc++.so.6.0.25)
==9400==    by 0x4C41100: mythread_wrapper (hg_intercepts.c:406)
==9400==    by 0x57852DD: start_thread (in /usr/lib64/libpthread-2.28.so)
==9400==    by 0x5A99132: clone (in /usr/lib64/libc-2.28.so)
==9400==  but before
==9400==    (the end of the thread)
==9400==  Address 0x602248 is in the BSS segment of /home/developer/mytmp/singleton/build/singleton
==9400== 
--9400-- 
--9400-- used_suppression:   2736 helgrind-glibc2X-005 /usr/local/libexec/valgrind/default.supp:1016
==9400== 
==9400== ERROR SUMMARY: 1800 errors from 2 contexts (suppressed: 2736 from 7)

3 Risposte

  • Re: Singleton ThreadSafe

    Facendo vari ipotesi e prove, sono giunto alla conclusione che 

    ho riscoperto l'acqua calda !!! :D

    qualsiasi cosa che allochiamo nello stack :
    dichiarazione di variabili, oggetti, allocazione dinamica etc
    (ad eccezione di static, smart pointer make_shared , ed altri barbatrucchi )
    quando viene eseguito il return lo stack viene deallocato.

    quindi se dichiaro un std::unique_lock<std::mutex> lock(mtx)
    all'interno della funzione 
    mi blocca il mutex automaticamente, (ok perfetto ...)
    ma poichè vive nello stack prima di eseguire il return
    viene deallocata la variabile lock, di conseguenza il mutex (mtx) diventa unlock
    e la risorsa puo' essere read/write da più threads nello stesso istante


    Lo scopo mio era che : tutte le risorse condivise
    venissero gestite all'interno della classe singleton
    ma non mi è venuta in mente niente. :(
    fatta ad eccezione di usare un std::atomic , 
    std::atomic si puo' bypassare l'utilizzo del mutex
    ma da quanto ho capito si puo' usare solo per int e bool
    non per gli oggetti. 

    questa soluzione è rozza,
    ma grazie ad essa riesco a dormire

     ordine chiamate :
     
     1 lock
     2 getInstance / getInstancePtr
     3 unlock
     
     ripulito il codice dai commenti 

     compilato sempre con le stesse opzioni di compilazione

      ho aggiunto un Delete move assignment no si sà mai se a qualcuno venisse in mente idee strane :)
     
     se si volesse usare la versione con il puntatore non dimentichiamoci di chiamare il
     delInstancePtr alla fine dei giochi
     
     posto anche il risultato di valgrind.

    main.cpp :

    #include <iostream>
    #include <vector>
    #include <thread>
    #include <mutex>
    
    namespace ns_singleton {
        template<typename T>
    	class singleton {
    	private:
    		singleton() { }
     		virtual ~singleton() { }
    		
      		//  Deleted copy constructor
            singleton( const singleton &other ) = delete;
      		//  Deleted copy assignment
            singleton& operator = ( const singleton&) = delete;
      		//  Deleted move constructor
            singleton( singleton &&other ) = delete;
      		//  Deleted move assignment
            singleton& operator = ( singleton&& ) = delete;
    
    		static std::mutex mtx;
            static T myInstance;
            
        public:
                
    //      static T* getInstancePtr() {
    //          if( !myInstancePtr )
    //             myInstancePtr = new T();
    //          return myInstancePtr;
    //      }
    
    //      static void delInstacePtr() { 
    //          if( myInstancePtr )
    //              delete myInstancePtr; 
    //      }
    
            static void lockInstance() { mtx.lock(); }
            static void unlockInstance() { mtx.unlock(); }
            static T& getInstance() { return myInstance; }
    
    	};
    
    //  template<typename T> T* singleton<T>::myInstancePtr = nullptr;
        template<typename T> T singleton<T>::myInstance;
        template<typename T> std::mutex singleton<T>::mtx;
    
    } /* namespace n_singleton */
    
    class myclass {
        public:
            myclass() : idata(0) { std::cout << " --->>> costructor my class <<<--- \n address idata : "<< std::hex << &idata << std::dec << "\n"; } 
            virtual ~myclass() { std::cout << " --->>> destructor my class <<<--- \n"; }
            void increment() { 
                ++idata; 
            }
            void display_data() { std::cout << " idata : " << idata << "\n"; }
        private:
            int idata = 0;
    };
    
    void thread_func() {
        for( int cnt = 0; cnt < 1000; cnt++) {
            ns_singleton::singleton<myclass>::lockInstance();
    //      ns_singleton::singleton<myclass>::getInstancePtr()->increment();
            ns_singleton::singleton<myclass>::getInstance().increment();
            ns_singleton::singleton<myclass>::unlockInstance();
        }
    }
    
    int main() {
        std::cout << "start singleton ...\n";
    	std::vector<std::thread> vth; 
    //	std::cout << " address instance : " << std::hex << ns_singleton::singleton<myclass>::getInstancePtr() << "\n";
    	std::cout << " address instance : " << std::hex << &ns_singleton::singleton<myclass>::getInstance() << "\n";
        for( int i = 0; i < 444; i++) {
            std::thread th1( thread_func );
            vth.emplace_back(std::move(th1));
        }	
    	std::cout << " num. of threads : " << std::dec << vth.size() << "\n";
        for(auto & elem : vth) 
            elem.join();
    //  ns_singleton::singleton<myclass>::getInstancePtr()->display_data();
        ns_singleton::singleton<myclass>::getInstance().display_data();
        
    //  ns_singleton::singleton<myclass>::delInstacePtr();
    	std::cout << "... done!\n";
    	return 0;
    }

    Esecuzione valgrind :

    $ valgrind --tool=helgrind --history-level=approx --log-file=out_helgrind_approx.txt -s ./singleton

    out_helgrind_approx.txt : 

    ==13031== Helgrind, a thread error detector
    ==13031== Copyright (C) 2007-2024, and GNU GPL'd, by OpenWorks LLP et al.
    ==13031== Using Valgrind-3.23.0 and LibVEX; rerun with -h for copyright info
    ==13031== Command: ./singleton
    ==13031== Parent PID: 6153
    ==13031== 
    ==13031== 
    ==13031== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1330772 from 7)
    --13031-- 
    --13031-- used_suppression: 1330772 helgrind-glibc2X-005 /usr/local/libexec/valgrind/default.supp:1016
    ==13031== 
    ==13031== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1330772 from 7)

    Non era proprop la soluzione che desideravo, il chiamante dovrà gestire lock e unlock 

    però così mi tornano i conti e valgrind aveva ragione.

  • Re: Singleton ThreadSafe

    Non ti ho risposto prima perche' "non ci avevo capito un'acciderbolina"

    MA

    ora ho letto una cosa TOTALMENTE SBAGLIATA:

    Non era proprop la soluzione che desideravo, il chiamante dovrà gestire lock e unlock 

    NON E' IL CHIAMANTE a dover gestire lock/unlock

    MA

    IL CHIAMATO!

    std::atomic, serve per eseguire operatzioni "atomiche" (somma sottrazione e poco altro) su un oggetto dalle dimensioni massime di 64 bit.
    NON per gestire l ""accesso concorrente a risorsa"".

    L'accesso concorrente a risorsa si gestiche con i mutex LATO RISORSA, NON lato chiamante.

    Hai DUE possibilita':

    1. MUTEX: SOLO un thread alla volta accede alla risorsa, o meglio, a quel pezzetto di codice.
    2. READ/WRITE LOCK: in lettura ci possono accede quanti thread vogliono, 
      MA UN SOLO thread alla volta puo' accedere in scrittura. Quando vuole accedere:
      1. aspetta che TUTTI i thread in lettura ancora in esecuzione abbiano terminato, e NON permette l'accesso a nessun nuovo thread
      2. accede in scrittura e fa quello che si deve fare
      3. al termine lascia liberi gli altri thread di procedere

    .

    Alla fin fine, dove sta' il "barbatrucco": 

    ti serve UN UNICO oggetto di sincronizzazione accessibile da TUTTI i thread per poter fare le sincronizzazioni.
    SE ogni thread ne usa uno per proprio conto, non sincronizzi un'acciderbolina.

    Ora, SE tu scrivi: "il chiamante dovrà gestire lock e unlock" e l'oggetto di sincronizzazione e' un bello STATICONE, 
    otteni esattamente lo stesso effetto, poiche' lo staticone non e' altro che la versione MAL SCRITTA 
    dell'oggetto di sincronizzazione gestito dalla risorsa condivisa. 
    La versione corretta e' APPICCICARE un oggetto di sincronizzazione DIRETTAMENTE alla risorsa a cui si vuol accedere 
    in modo sincronizzato.

    --

    Ora, COME si implementa il tutto, DIPENDE dalle scelte programmatore. ;-)
    MA la parte "filosofica" (e' la RISORSA a gestire l'accesso, NON chi ci vuole accedere) 
    NON CAMBIA.

    --

    Ed a questo punto avrai capito che tutta la tua elucubrazione sullo stack ha un senso (ANCHE se l'elucubrazione e' un po' allucinatoria ;-))
    Il problema NON E' lo stack, MA il fatto che usavi un oggetto di sincronizzazione DIVERSO per ogni thread!!
    Al che, sincronizzavi ogni thread SOLO CON SE STESSO. Ma va! ;-)

    --

    PThreads non sono male. MA ormai le ultime versioni di C++ hanno la gestione dei thread integrata direttamente nella libreria standard.
    Comunque guardati anche "boost" se non la conosci gia'.

    --

    Ci sei quasi! 
    Ti consiglio di approffondire MEGLIO come funzionano i thread e quali siano le problematiche nella programmazione concorrente.
    SEMBRA facile, ma di casini incasinati se ne possono fare in quantita' industriale.

    Tempo fa ho speso una SETTIMANA per risolvere una castronata fatta da un "consulente super esperto in programmazione concorrente".
    Castronata da ragazzetto alle prime armi ! Altro che super esperto !

  • Re: Singleton ThreadSafe

    Ciao e grazie per aver risposto

    NON E' IL CHIAMANTE a dover gestire lock/unlock

    MA

    IL CHIAMATO!

    volevo dire che nella soluzione che ho trovato, all'interno del thread dovrà chiamare lock e unlock prima e dopo la chiamata getInstance affinchè non ci sia data race 

    purtroppo non posso usare boost . :( non posso importare nulla. 

    1. MUTEX: SOLO un thread alla volta accede alla risorsa, o meglio, a quel pezzetto di codice.

    2. READ/WRITE LOCK: in lettura ci possono accede quanti thread vogliono, 
    MA UN SOLO thread alla volta puo' accedere in scrittura. Quando vuole accedere:

    1. aspetta che TUTTI i thread in lettura ancora in esecuzione abbiano terminato, e NON permette l'accesso a nessun nuovo thread
    2. accede in scrittura e fa quello che si deve fare
    3. al termine lascia liberi gli altri thread di procedere

    intendi una cosa del genere , gestirlo a livello di thread e non dentro la classe singleton ?

    ...
    std::mutex mtx_thread; // mutex condiviso tra più threads
    void thread_func() {
        for( int cnt = 0; cnt < 1000; cnt++) {
    		mtx_thread.lock();    
            ns_singleton::singleton<myclass>::getInstance().increment();
            // ... faccio quello che devo fare .... 
            mtx_thread.unlock();
        }
    }
    ....

    non mi fido, anche se solo in lettura, accedere allo stesso indirizzo da più threads senza mutex, secondo me questo è sbagliato.

    class myclass {
        public:
        ...
            int getiData() { return idata; }
        private:
            int idata = 0;
    };
        
    void thread_func() {
        for( int cnt = 0; cnt < 1000; cnt++) {		
            int temp = ns_singleton::singleton<myclass>::getInstance().getiData();
            temp --;        
        }
    }

    purtroppo questa zozzeria si trova in molti esempi che si spacciano per thread safe, ma è sbagliato .

    dal c++11 la keyword static è thread safe. l'istruzione static T mylocalInstance è protetta 

    ma return mylocalInstance non lo è . è corretto quello che dico ? 

    template<typename T>
    class singleton {
    ...
        public:
             static T& getInstance() { 
                  static T mylocalInstance; 
                  return mylocalInstance;
             }
    }
Devi accedere o registrarti per scrivere nel forum
3 risposte