2
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;

class mySingleton {
    private:
        mySingleton() {
            cout<<"Singleton initialization"<<endl;
            this_thread::sleep_for(chrono::milliseconds(10000));
            cout<<"Singleton initialization terminated"<<endl;
        }   
    public:
        static mySingleton* getInstance() {
            static mySingleton* instance = NULL;
            static mutex mtx;
            if(instance == NULL){
                mtx.lock();
                if(instance == NULL)    
                    instance = new mySingleton();
                mtx.unlock();
            }
         return instance;
        }
};
void *create(void* x) {
    this_thread::sleep_for(chrono::milliseconds(3000));
    *(mySingleton**)x = mySingleton::getInstance();
    pthread_exit(NULL);
}
int main(){
    mySingleton* x = NULL;
    pthread_t t1, t2;
    pthread_create(&t1, NULL, create, (void *)&x);
    cout << "thread 1 created"<<endl;
    this_thread::sleep_for(chrono::milliseconds(1000));
    cout << "thread 2 is about to start"<<endl;

    while(x == NULL){
        cout <<"t2: instance not created yet"<<endl;
        this_thread::sleep_for(chrono::milliseconds(1000));
    }
    cout << "t2: instance created"<<endl;
    cout << "main thread\n";
    pthread_exit(NULL);
    return 0;
}

the while statement loops forever, this is why (I suppose) thread t1 updates a local copy of the pointer, but how can I make main thread, or any other thread, see that the mySingleton object has been created?

Luigi2405
  • 677
  • 2
  • 7
  • 12
  • Are you married to using pthread? Can we change the while loop to synchronize? – Yakk - Adam Nevraumont Jan 04 '20 at 09:54
  • @Yakk-AdamNevraumont I did that way because I want to know if the pointer to mySingleton stops being null as soon as contructor is called or when it terminates (singleton pattern implementation varies accordingly and not for issues related to mutual exclusion). I know that in a multithread environment there is the need for a proper synchronization, but this is not the point of my question – Luigi2405 Jan 04 '20 at 10:10
  • Are you sure the loop condition should be `x != NULL`, not `x == NULL`? By the way, I suggest you should initialize `x` first like `mySingleton* x = NULL;` for in case the system don't start the new threads for over 1 seconds. – MikeCAT Jan 04 '20 at 10:11
  • @MikeCAT my bad. `while` condition was wrong, I updated it and it happens that is ALWAYS null. I edited the code in the question. In other words, same problem as before – Luigi2405 Jan 04 '20 at 10:18
  • I guess this is caused by wrong optimization: the compiler seems thinking `x` won't be updated while it is actually updated by another thread, omitting the check and turning the loop to a simple infinite loop. – MikeCAT Jan 04 '20 at 10:28
  • @MikeCAT I added `volatile` modifier to `x` but I still have the same problem: x is always NULL – Luigi2405 Jan 04 '20 at 10:31
  • @luigi Any race condition in C++ is undefined behaviour; reafing a pointer need not actually read it is there is no synchronization and no local way for it to change. Volatile doesn't remove the UB; thr racr condition still exists. Compiler is free to do **anything**. – Yakk - Adam Nevraumont Jan 04 '20 at 18:51
  • @Yakk-AdamNevraumont like I said I know there's mutual exclusion problem and in any implementation of a singleton pattern special care must reserved to avoid such a situation BUT my doubt here was of another nature. In particular, even if mutual exclusion is guaranteed (this is not the case but it doesn't matter for the purpose of this question), when a thread tries to get the instance of mySingleton and this is being created exactly at this time upon antoher thread invoked for the first time `getMySingleton()`, what it gets depends on the fact that the pointer to `x` stops being null as soon – Luigi2405 Jan 05 '20 at 08:29
  • as the the constructor is invoked or when it terminates. With this code I understood that the latter occurs, and there is not the need of a dummy pointer in initialization phase (like I read somewhere in the internet). – Luigi2405 Jan 05 '20 at 08:31
  • 1
    @luigi You are asking a when question, and the C++ memory model makes that complex; `x` stops being null to a bit of code if and only if it being set to null is sufficiently synchronized with that bit of code. The degree to which thibgs can be incoherant is very large unless you talk about synchronization. I get that isn't what you are asking, but I am saying any answer that doesn't cover that is nonsense under the C++ standard. Naive models of "when" **do not work** in the C++ memory model. `x` "being null" is indeterminate from another thread until you add synchronization, period. – Yakk - Adam Nevraumont Jan 05 '20 at 14:15
  • @Yakk-AdamNevraumont I just edited the code to guarantee synchronization. Now can you answer? – Luigi2405 Jan 05 '20 at 14:40

2 Answers2

3

As you say,

x = mySingleton::getInstance();

is just updating the local copied argument.

You should pass the pointer to variable x in main()

pthread_create(&t1, NULL, create, (void *)&x); // add & before x

and update what is pointed at by the passed pointer.

*(mySingleton**)x = mySingleton::getInstance(); // add cast and dereference
MikeCAT
  • 73,922
  • 11
  • 45
  • 70
0

You can, but you must be careful. Synchronization and happens-before are required, or you wander into undefined behavior territory.

Undefined behavior can include "it appears to work", but it breaks when you change unrelated code, linking order, or update your compiler.

First, we can clean up mySingleton:

class mySingleton {
private:
  mySingleton() {
    std::cout<<"Singleton initialization"<<std::endl;
    std::this_thread::sleep_for(chrono::milliseconds(10000));
    std::cout<<"Singleton initialization terminated"<<std::endl;
  }
public:
  static mySingleton* getInstance() {
    static mySingleton* instance = new mySingleton;
    return instance;
  }
};

"magic statics" guarantee that the initialization of a static local variable is done exactly once, no matter how many threads attempt it, and nobody will miss it.

This doesn't however provide a guarantee that your worker threads operations will be visible from the main thread.

We should replace this pthread task:

void *create(void* x) {
  this_thread::sleep_for(chrono::milliseconds(3000));
  *(mySingleton**)x = mySingleton::getInstance();
  pthread_exit(NULL);
}

with something a bit less C and more C++.

int main(){
  mySingleton* x = NULL;
  std::thread t1 = ([&x]{ 
    this_thread::sleep_for(chrono::milliseconds(3000));
    x = mySingleton::getInstance();
  });
  std::cout << "thread 1 created"<<std::endl;
  std::this_thread::sleep_for(chrono::milliseconds(1000));
  std::cout << "thread 2 is about to start"<<std::endl;

  while(x == NULL){
    std::cout <<"t2: instance not created yet"<<std::endl;
    this_thread::sleep_for(chrono::milliseconds(1000));
  }
  std::cout << "main thread\n";
  return 0;
}

I've gotten rid of the pthread noise and replaced it with a simpler std::thread implementation.

The above code contains a race condition, as did the original code I based it off of. You write to x from one thread and read from another without synchronization; that is a race condition in C++, and the result is undefined behavior.

We can fix this a few ways.

int main(){
  std::future<mySingleton*> x = std::async( std::launch::async, []{ 
    this_thread::sleep_for(chrono::milliseconds(3000));
    return mySingleton::getInstance();
  });
  std::cout << "thread 1 created"<<std::endl;
  std::this_thread::sleep_for(chrono::milliseconds(1000));
  std::cout << "thread 2 is about to start"<<std::endl;

  using namespace std::chrono::literals;
  while(x.wait_for(0s) == std::future_status::timeout){
    std::cout <<"t2: instance not created yet"<<std::endl;
    this_thread::sleep_for(1000ms);
  }
  std::cout << "main thread\n";
  return 0;
}

here we make the worker task populate a std::future of what we want. This lets us wait for it to be ready, and when it is we can x.get() to get a synchronized mySingleton* that has been initialized.

We can create an explicit thread instead:

  std::promise<mySingleton*()> pr;
  std::future<mySingleton*> x = pr.get_future();
  std::thread t1 = std::thread( [&pr]{ 
    this_thread::sleep_for(chrono::milliseconds(3000));
    pr.set_value( mySingleton::getInstance() );
  });

and leave the rest of the code alone.

If we don't want x to be a future (or shared future), we can make it a mutex wrapped value:

template<class T>
struct shared_mutex_wrapped {
  template<class F>
  auto read( F&& f ) const {
    auto l = lock();
    return f(t);
  }
  template<class F>
  auto write( F&& f ) {
    auto l = lock();
    return f(t);
  }
private:
  mutable std::shared_mutex m;
  T t = {};
  auto lock() const { return std::shared_lock< std::shared_mutex >( m ); }
  auto lock() { return std::unique_lock< std::shared_mutex >( m ); }
};

we can then get code similar to your original flow:

mutex_wrapped<mySingleton*> x;
std::thread t1 = ([&x]{ 
  this_thread::sleep_for(chrono::milliseconds(3000));
  x.write([](auto&x){ x= mySingleton::getInstance();} )
});
std::cout << "thread 1 created"<<std::endl;
std::this_thread::sleep_for(chrono::milliseconds(1000));
std::cout << "thread 2 is about to start"<<std::endl;

while(x.read([](auto& x){ return x==NULL; }) ){
  std::cout <<"t2: instance not created yet"<<std::endl;
  this_thread::sleep_for(chrono::milliseconds(1000));
}
std::cout << "main thread\n";
return 0;

where access to x is guarded by reader/writer synchronization.

Any attempt to write directly to x in one thread, and read directly without synchronization from x in another thread, makes your program execute undefined behavior under the C++ standard.

If you do this, it might "appear to work", because that is one possible symptom of undefined behavior. But to actually work -- to be a well defined program that does what you want it to do -- you must arrange that the shared value x have its access be synchronized somehow. It must be possible for the code initializing it to formally "happen-before" the code reading it, and no reads can occur the writing is occurring that doesn't happen-before or happen-after.

Another approach is std::atomic<mySingleton*>:

std::atomic<mySingleton*> x;
std::thread t1 = ([&x]{ 
  this_thread::sleep_for(chrono::milliseconds(3000));
  x = mySingleton::getInstance();
});
std::cout << "thread 1 created"<<std::endl;
std::this_thread::sleep_for(chrono::milliseconds(1000));
std::cout << "thread 2 is about to start"<<std::endl;

while(x.get() == nullptr){
  std::cout <<"t2: instance not created yet"<<std::endl;
  this_thread::sleep_for(chrono::milliseconds(1000));
}
std::cout << "main thread\n";
return 0;

which requires even less changes to your main function.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524