Thread-Safe Initialization of Data

Contents[Show]

In case the data is not modified when shared between threads, the story is simple. The data has only to be initialized in the thread safe way. It is not necessary to use an expensive lock for each access.

 

There are three ways in C++ to initialize variables in a thread safe way.

  1. Constant expressions
  2. The function std::call_once in combination with the flag std::once_flag
  3. Static variables with block scope

Constant expressions

Constant expressions are expressions which the compiler can initialize during compile time. So, they are implicit thread safe. By using the keyword constexpr in front of the expression type makes it constant expression.

constexpr double pi=3.14; 

In addition, user defined types can also be constant expressions. For those types, there are a few restrictions in order to initialize them at compile time.

  • They must not have virtual methods or a virtual base class.
  • Their constructor must be empty and itself be a constant expression.
  • Their methods, which can be callable at compile time, must be constant expressions.

My struct MyDouble satisfies all these requirements. So it's possible to instantiate objects of MyDouble at compile time. This instantiation is thread safe.

struct MyDouble{
  constexpr MyDouble(double v): val(v){}
  constexpr double getValue(){ return val; }
private:
  double val
};

constexpr MyDouble myDouble(10.5);
std::cout << myDouble.getValue() << std::endl;

The function call_once in combination with the once_flag

By using the std::call_once function, you can register all callable. The std::once_flag ensures, that only one registered function will be invoked. So, you can register more different functions via the once_flag. Only one function is called.

The short example shows  the application of std::call_once and std::once_flag.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// callOnce.cpp

#include <iostream>
#include <thread>
#include <mutex>
 
std::once_flag onceFlag;
 
void do_once(){
  std::call_once(onceFlag, [](){ std::cout << "Only once." << std::endl; });
}
 
int main(){
  
  std::cout << std::endl;
  
  std::thread t1(do_once);
  std::thread t2(do_once);
  std::thread t3(do_once);
  std::thread t4(do_once);
 
  t1.join();
  t2.join();
  t3.join();
  t4.join();
  
  std::cout << std::endl;

}

 

The program starts four threads (lines 17 - 20). Each of them should invoke the function do_once. The expected result is that the string "only once" is displayed only once.

callOnce

The famous singleton pattern guarantees only one instance of an object will be created. That is a challenging task in multithreaded environment. But, thanks to std:.call_once and std:once_flag the job is a piece of cake. Now the singleton is initialized in a thread safe way. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// singletonCallOnce.cpp

#include <iostream>
#include <mutex>

class MySingleton{

  private:
    static std::once_flag initInstanceFlag;
    static MySingleton* instance;
    MySingleton()= default;
    ~MySingleton()= default;

  public:
    MySingleton(const MySingleton&)= delete;
    MySingleton& operator=(const MySingleton&)= delete;

    static MySingleton* getInstance(){
      std::call_once(initInstanceFlag,MySingleton::initSingleton);
      return instance;
    }

    static void initSingleton(){
      instance= new MySingleton();
    }
};

MySingleton* MySingleton::instance= nullptr;
std::once_flag MySingleton::initInstanceFlag;


int main(){

  std::cout << std::endl;

  std::cout << "MySingleton::getInstance(): "<< MySingleton::getInstance() << std::endl;
  std::cout << "MySingleton::getInstance(): "<< MySingleton::getInstance() << std::endl;

  std::cout << std::endl;

}

 

At first the static std::once_flag. This is in the line 9 declared and initialized in the line 29. The static method getInstance (line 28 - 21) uses the flag in order to ensures, that the static method initSingleton (line 23 - 25) is executed exactly once. In the body of the method, the singleton is created.

The output of the program is not so thrilling. The MySingleton::getIstance() method displays the address of the singleton.

singletonCallOnce

The static story goes on.

Static variables with block scope

Static variables with block scope will be created exactly once. This characteristic is the base of the so called Meyers Singleton, named after Scott Meyers.  This is by far the most elegant implementation of the singleton pattern.

#include <thread>

class MySingleton{
public:
  static MySingleton& getInstance(){
    static MySingleton instance;
    return instance;
  }
private:
  MySingleton();
  ~MySingleton();
  MySingleton(const MySingleton&)= delete;
  MySingleton& operator=(const MySingleton&)= delete;

};

MySingleton::MySingleton()= default;
MySingleton::~MySingleton()= default;

int main(){

  MySingleton::getInstance();

}

 

By using the keyword default, you can request special methods from the compiler. They are Special because only compiler can create them. With delete, the result is, that the automatically generated methods (constructor, for example) from the compiler will not be created and, therefore, can not be called. If you try to use them you'll get an compile time error. What's the point of the Meyers Singleton in multithreading programs? The Meyers Singleton is thread safe.

A side note: Double-checked locking pattern

Wrong beliefe exists, that an additional way for the thread safe initialization of a singleton in a multithreading environment is the double-checked locking pattern. The double-checked locking pattern is - in general -  an unsafe way to initialize a singleton. It assumes guarantees in the classical implementation, which aren't given by the Java, C# or C++ memory model. The assumption is, that the access of the singleton is atomic.

But, what is the double-checked locking pattern? The first idea to implement the singleton pattern in a thread safe way, is  to protected the initialization of the singleton by a lock.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
mutex myMutex;

class MySingleton{
public:  
  static MySingleton& getInstance(){    
    lock_guard<mutex> myLock(myMutex);      
    if( !instance ) instance= new MySingleton();    
    return *instance;  
  }
private:  
  MySingleton();  
  ~MySingleton();  
  MySingleton(const MySingleton&)= delete;  
  MySingleton& operator=(const MySingleton&)= delete;
  static MySingleton* instance;
};
MySingleton::MySingleton()= default;
MySingleton::~MySingleton()= default;
MySingleton* MySingleton::instance= nullptr;

 

Any issues? Yes and no. The implementation is thread safe. But there is a great performance penalty. Each access of the singleton in line 6 is protected by an expansive lock. That applies also for the reading access. Most time it's not necessary. Here comes the double-checked locking pattern to our rescue.

1
2
3
4
5
6
7
static MySingleton& getInstance(){    
  if ( !instance ){      
    lock_guard<mutex> myLock(myMutex);      
    if( !instance ) instance= new MySingleton();    
    return *instance;  
  }
}

 

I use inexpensive pointer comparison  in the line 2 instead of an expensive lock a. Only if I get a null pointer, I apply the expensive lock on the singleton (line 3). Because there is the possibility that another thread will initialize the singleton between the pointer comparison (line 2) and the lock (line3), I have to perform an additional pointer comparison the on line 4. So the name is clear. Two times a check and one time a lock.

Smart? Yes. Thread safe? No.

What is the problem? The call instance= new MySingleton() in line 4 consists of at least three steps.

  1. Allocate memory for MySingleton
  2. Create the MySingleton object in the memory
  3. Let instance refer to the MySingleton object

The problem: there is no guarantee about  the sequence of these steps. For example, out of optimization reasons, the processor can reorder the steps to the sequence 1,3 and 2. So, in the first step the memory will be allocated and in the second step, instance refers to an incomplete singleton. If at that time another thread tries to access the singleton, it compares the pointer and gets the answer true. So, the other thread has the illusion that it's dealing with a complete singleton.

The consequence is simple: program behaviour is undefined.

What's next?

At first, I thought, I should continue in the next post with the singleton pattern. But to write about the singleton pattern, you should have a basic knowledge of the memory model. So I continue in the sequence of my German blog. The next post will be about-thread local storage. In case we are done with the high end API of multithreading in C++, I'll go further with the low end API. (Proofreader Alexey Elymanov)

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

Comments   

0 #1 visit 2016-06-28 04:32
I genuinely appreciate your work, Great post.
Quote
0 #2 goo.gl 2016-06-28 04:41
I got this web page from my pall who informed me regarding
this websitfe and now this time I am visioting this site and
reading very informative posts here.
Quote
0 #3 visit 2016-07-02 14:37
I really treasure your pikece of work, Great post.
Quote
0 #4 Dianne 2016-07-06 13:03
I do not even know how I finished up here, but I believed this publish was
once good. I don't understand whho you're but definitely you are going to a
famous bloggewr iff you are not already. Cheers!
Quote
0 #5 twitter.com 2016-07-11 05:05
I'm still learning from you, ass I'm improving myself.

I definitely love reading everything that is posted on your site.Keep
the posts coming. I liked it!
Quote

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 559

All 255864

Currently are 349 guests and no members online