The CPP Memory Model
The CPP Memory Model
Memory Model
Rainer Grimm
Training, Coaching and Technology Consulting
www.grimm-jaud.de
Multithreading with C++
• Synchronization of threads
Expert Levels
The C++ Memory Model
The Contract
Atomics
Singleton Pattern
The C++ Memory Model
The Contract
Atomics
Singleton Pattern
The Contract
Number of potential
interleaving's grows
exponentially
Acquire-release semantic
Synchronization of atomics
(between threads)
Relaxed semantic
Weak Memory Model
Weak guarantees
The C++ Memory Model
The Contract
Atomics
Singleton Pattern
Atomics
Spinlock
class Spinlock{ Spinlock spin;
std::atomic_flag flag; // Mutex spin;
public: void workOnResource(){
spin.lock();
Spinlock():flag(ATOMIC_FLAG_INIT){} sleep_for(seconds(2));
spin.unlock();
void lock(){ }
while(flag.test_and_set()); int main({
} thread t(workOnResource);
thread t2(workOnResource);
void unlock(){ t.join();
flag.clear(); t2.join();
} }
};
Atomics: std::atomic_flag
Spinlock Mutex
Atomics: std::atomic<bool>
std::vector<int> mySharedWork;
std::mutex mutex_; void waitingForWork(){
unique_lock<mutex> lck(mutex_);
std::condition_variable condVar;
condVar.wait(lck, []{ return dataReady; });
mySharedWork[1]= 2;
bool dataReady;
}
std::vector<int> mySharedWork;
std::atomic<bool> dataReady(false);
void setDataReady(){
mySharedWork={1,0,3}; int main(){
} thread t2(setDataReady);
t1.join();
} }
mySharedWork[1]= 2; } // 1 2 3
sequenced-before
synchronizes-with
Atomics: std::atomic
The Contract
Atomics
Singleton Pattern
Synchronization and Ordering
• read operations:
memory_order_acquire and memory_order_consume
• write operations:
memory_order_release
• read-modify-write operations:
memory_order_acq_rel and memory_order_seq_cst
• Sequential consistency
• Global ordering of all threads
memory_order_seq_cst
• Acquire-release semantic
• Ordering between read and write operations on the same atomic
memory_order_consume, memory_order_acquire,
memory_order_release, and memory_order_acq_rel
• Relaxed semantic
• No synchronizations and ordering constraints
memory_order_relaxed
Synchronization and ordering
Possible
interleaving's
Synchronization and Ordering
Acquire-release semantic
• A release-operation on a atomic synchronizes with a acquire-
operation on the same atomic and establishes in addition an
ordering constraint.
• Acquire-operation:
• Read-operation (load or test_and_set)
• Release-operation:
• Write-operation (store or clear)
• Ordering constraints:
• Read- and write-operations can not be moved before an acquire-
operation.
• Read- and write-operations can not be moved after a release-
operation.
Synchronization and Ordering
Thread 1
Thread 2
Thread 3
Synchronization and Ordering
Acquire-operations
Locking of a mutex
Waiting for a condition
variable
Starting of a thread
Release-operations
Unlocking of a mutex
Notification of a condition
variable
join-call on a thread
Synchronization and Ordering
class Spinlock{
std::atomic_flag flag;
public:
Spinlock(): flag(ATOMIC_FLAG_INIT){}
void lock(){
while(flag.test_and_set(memory_order_acquire));
}
void unlock(){
flag.clear(std::memory_order_release);
}
};
Synchronization and Ordering
Consume-release semantic
• Consume-release semantic is the acquire-release semantic
without ordering constraints.
Relaxed semantic
• There are no synchronization and ordering constraints. The
operations are only atomic.
• Rule
• Atomic operations with stronger memory orderings are used to
order atomic operations with relaxed semantic.
The Contract
Atomics
Singleton Pattern
Singleton
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;
...
MySingleton::getInstance();
Performance problem
Sequential Consistency
class MySingleton{
public:
static MySingleton* getInstance(){
MySingleton* sin= instance.load();
if ( !sin ){
std::lock_guard<std::mutex> myLock(myMutex);
sin= instance.load(std::memory_order_relaxed);
if( !sin ){
sin= new MySingleton();
instance.store(sin);
}
}
return sin;
}
private:
static std::atomic<MySingleton*> instance;
static std::mutex myMutex;
. . .
Acquire-Release Semantic
class MySingleton{
public:
static MySingleton* getInstance(){
MySingleton* sin= instance.load(std::memory_order_acquire);
if ( !sin ){
std::lock_guard<std::mutex> myLock(myMutex);
sin= instance.load(std::memory_order_relaxed);
if( !sin ){
sin= new MySingleton();
instance.store(sin,std::memory_order_release);
}
}
return sin;
}
. . .
Meyers Singleton
class MySingleton{
public:
static MySingleton& getInstance(){
static MySingleton instance;
return instance;
}
private:
MySingleton()= default;
~MySingleton()= default;
MySingleton(const MySingleton&)= delete;
MySingleton& operator=(const MySingleton&)= delete;
};
You can find std::call_once and the rest of the details here:
Thread safe initialization of a singleton.
Singleton: The Performance Test
My conclusions
The Contract
Atomics
Singleton Pattern
Further Information
• Contact
• @rainer_grimm (Twitter)
• [email protected]