It’s very interesting to compare the active waiting of a spinlock with the passive waiting of a mutex. Let's continue our discussing from the previous lesson and make a comparison between these two.

What will happen to the CPU load if the function workOnResource locks the spinlock for 2 seconds (lines 24 - 26)?

// spinLockSleep.cpp

#include <iostream>
#include <atomic>
#include <thread>

class Spinlock{
  std::atomic_flag flag;
public:
  Spinlock(): flag(ATOMIC_FLAG_INIT){}

  void lock(){
    while( flag.test_and_set() );
  }

  void unlock(){
    flag.clear();
  }
};

Spinlock spin;

void workOnResource(){
  spin.lock(); //here
  std::this_thread::sleep_for(std::chrono::milliseconds(2000));
  spin.unlock();
  std::cout << "Work done" << std::endl;
}

int main(){

  std::thread t(workOnResource);
  std::thread t2(workOnResource);

  t.join();
  t2.join();

}

According to the theory, one of the four cores of PC will be fully utilized, and that’s exactly what happened as the load of one core reaches 100% on my PC. Each time, a different core performs busy waiting.

Now, I will use a mutex instead of a spinlock. Let’s see what happens.

// mutex.cpp
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mut;

void workOnResource(){
  mut.lock();
  std::this_thread::sleep_for(std::chrono::milliseconds(5000));
  mut.unlock();
  std::cout << "Work done" << std::endl;
}

int main(){

  std::thread t(workOnResource);
  std::thread t2(workOnResource);

  t.join();
  t2.join();

}

Although I executed the program several times, I did not observe a significant load on any of the cores.

In the next lesson, let’s go one step further from the basic building block std::atomic_flag to the more advanced atomics: the class template std::atomic. The various partial and full specializations for bools, integral types, and pointers provide a more powerful interface than std::atomic_flag. The downside is that you do not have the guarantee that these specializations are lock-free.