The Atomic Boolean

Contents[Show]

The remaining atomics - in contrast to std::atomic_flag - are partial or full specialisations of the class template std::atomic. Let's start with std::atomic<bool>.

 

std::atomic<bool>

std::atomic<bool> has a lot more to offer than std::atomic_flag. It can explicitly be set to true or false. That's enough to synchronise two threads. So I can simulate condition variables with atomic variables.

Let's first have a look at condition variables.

 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
42
43
44
45
46
47
48
49
// conditionVariable.cpp

#include <condition_variable>
#include <iostream>
#include <thread>
#include <vector>

std::vector<int> mySharedWork;
std::mutex mutex_;
std::condition_variable condVar;

bool dataReady;

void waitingForWork(){
    std::cout << "Waiting " << std::endl;
    std::unique_lock<std::mutex> lck(mutex_);
    condVar.wait(lck,[]{return dataReady;});
    mySharedWork[1]= 2;
    std::cout << "Work done " << std::endl;
}

void setDataReady(){
    mySharedWork={1,0,3};
    {
        std::lock_guard<std::mutex> lck(mutex_);
        dataReady=true;
    }
    std::cout << "Data prepared" << std::endl;
    condVar.notify_one();
}

int main(){
    
  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

  t1.join();
  t2.join();
  
   for (auto v: mySharedWork){
      std::cout << v << " ";
  }
      
  
  std::cout << "\n\n";
  
}

 

And now the pendant with atomic booleans.

 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
42
43
44
// atomicCondition.cpp

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

std::vector<int> mySharedWork;
std::atomic<bool> dataReady(false);

void waitingForWork(){
    std::cout << "Waiting " << std::endl;
    while ( !dataReady.load() ){             // (3)
        std::this_thread::sleep_for(std::chrono::milliseconds(5));
    }
    mySharedWork[1]= 2;                      // (4)
    std::cout << "Work done " << std::endl;
}

void setDataReady(){
    mySharedWork={1,0,3};                    // (1)
    dataReady= true;                         // (2)
    std::cout << "Data prepared" << std::endl;
}

int main(){
    
  std::cout << std::endl;

  std::thread t1(waitingForWork);
  std::thread t2(setDataReady);

  t1.join();
  t2.join();
  
  for (auto v: mySharedWork){
      std::cout << v << " ";
  }
      
  
  std::cout << "\n\n";
  
}

 

What guarantees, that line 17 will be executed after the line 14? Or to say it more general, that the thread t1 will execute mySharedWork[1]= 2 (line 17) after thread t2 had executed mySharedWork={1,0,3} (line 22). Now it gets more formal.

  • Line22 (1) happens-before line 23 (2)
  • Line 14 (3) happens-before line 17 (4)
  • Line 23 (2) synchronizes-with line 14 (3)
  • Because happens-before is transitive, it follows: mySharedWork={1,0,3} (1) happens-before mySharedWork[1]= 2 (4)

In want to explicitly mention one point. Because of the condition variable condVar or the atomic dataReady, the access to the shared variable mySharedWork is synchronised. This holds although mySharedWork is not protected by a lock or itself an atomic.

Both programs produce the same result for mySharedWork.

conditionVariableAtomic

Push versus pull principle

Obviously, I cheated a little. There is one difference between the synchronisation of the threads with the condition variable and the atomic boolean. The condition variable notifies the waiting thread (condVar.notify()), that it should proceed with its work. But the waiting thread with the atomic boolean checks, if the sender is done with its work (dataRead= true).

The condition variable notifies the waiting thread (push principle). The atomic boolean repeatedly asks for the value (pull principle).

compare_exchange_strong and compare_exchange_weak

std::atomic<bool> and the fully or partially specialisations of std::atomic supports the bread and butter of all atomic operations: compare_exchange_strong. This function has the syntax: bool compare_exchange_strong(T& expected, T& desired). Because this operation compares and exchanges in one atomic operation a value, is often called compare_and_swap (CAS). This kind of operation is in a lot of programming languages available. Of course, the behaviour may differ a little.

A call of atomicValue.compare_exchange_strong(expected, desired) obeys the following strategy. In case the atomic comparison of atomicValue with expected returns true, the value of atomicValue is set in the same atomic operation to desired. If the comparison returns false, expected will be set to atomicValue. The reason why the operation compare_exchange_strong is called strong is simple. There is a method compare_exchange_weak. This weak version can spuriously fail. That mean, although *atomicValue == expected holds, the weak variant returns false. So you have to check the condition in a loop: while ( !atomicValue.compare_exchange_weak(expected, desired) ). The reason for the weak form is performance. On some platforms, the weak is faster than the strong variant.

What's next?

The next post will be about the class template std::atomic. So I write about the different specialisations for integrals and pointer. They provide a richer interface than the atomic boolean std::atomic<bool>. (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 #11 Carissa 2017-03-02 12:28
Thanks for sharing such a good idea, article is fastidious,
thats why i havce read it completely
Quote
0 #12 Norine 2017-03-08 07:20
I was reading some of your posts on this intternet
site and I blieve this site is real informative!
Keep on posting.
Quote
0 #13 Jenni 2017-04-16 22:52
Hello.This article was extremely remarkable, particularly since I
was searching for thoughts on this matter last couple of days.
Quote
0 #14 Bobbie 2017-04-18 04:12
Hello There. I found your blog using msn. This iss a really well written article.
I'll be sure to bookmark it and return to read more of your useful information. Thanks
for the post. I'll certainly return.
Quote
0 #15 thunderwiiring 2017-06-06 20:12
I like your content! very much!!
Quote

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 50

All 362740

Currently are 124 guests and no members online