0
#include "boost/smart_ptr/detail/spinlock.hpp"
boost::detail::spinlock lock;
main(){
    std::lock_guard<boost::detail::spinlock> guard(lock);
    while(true)
        {
                i=i+100;
        }
}

Machine details:

CPU(s): 2

On-line CPU(s) list: 0,1

Thread(s) per core: 1

Core(s) per socket: 2

Socket(s): 1

Of the above code, When i ran :

First instance => it took 100% of the cpu( as per top command)

Second instance => it took 97-98% and the sum of these two instances show approx 195%-197%

Third instance => it took ~47-50% and the sum of these three showed close to 200% , by adjusting cpu consumption of first two instances.

My assumption was that, once the spin lock acquires the cpu, it doesn't get prempted by cpu(it doesn't get switched by cpu by scheduling some other thread for this time keeping the thread(spin locked) in scheduling queue), and hence i was expecting third instance to fail. But it ran showing that the first two processes thread's were prempted.

Where am i getting it wrong?

anurag86
  • 1,635
  • 1
  • 16
  • 31
  • 1
    Please show a [mre] with the actual code you are using. There is no contention on the lock in your example so your code will pass straight through to the while loop – Alan Birtles Apr 28 '20 at 07:03
  • @AlanBirtles It's the same code i am using to test my understanding. The results are from the same piece of code. Yes, there is no contention but wouln't the implementation of spin_lock make sure that the context switch for thread doesn't take place at all irrespective of the contention? How would contention matter here? – anurag86 Apr 28 '20 at 07:10
  • If there's only one thread locking the lock you might as well delete the lock. You are simply measuring the performance of the while loop – Alan Birtles Apr 28 '20 at 07:12
  • @AlanBirtles : Right. It would spin(wait), not leaving the thread , only if some other thread has acquired the cpu. So, ideally i should have tested it three threads spinning on the lock – anurag86 Apr 28 '20 at 07:15
  • I think you misunderstand what a spin lock is. It is a thread synchronisation primitive which continuously tests its value to check whether it is unlocked. Once it is locked it doesn't do anything until it is unlocked. Your code would behave exactly the same without the spin lock – Alan Birtles Apr 28 '20 at 08:40

1 Answers1

1

I think you misunderstand what a spin lock is. It's not much more complicated than this:

class SpinLock {
public:
    void lock() {
        while (is_locked) { /*do nothing*/ }
        // ...MAGIC HAPPENS HERE...
        is_locked = true;
    }

    void unlock() {
        is_locked = false;
        // ...SUBTLE magic happens here...
    }

private:
    bool is_locked = false;
};

The MAGIC is code that uses special machine instructions* to ensure that, if more than one thread is "spinning" in the while loop at the same time, only one of them will get to see is_locked == false and exit the loop when some other thread calls the unlock() function.

My assumption was that, once the spin lock acquires the cpu...

There's nothing in a spin lock that can "acquire" a CPU. It's just code that gets run by a CPU, no different from any other code in your program. The operating system (OS) decides which thread to run on which CPU and when, and nothing that a spin lock does can influece that.

...it doesn't get preempted by CPU.

A CPU doesn't preempt anything. A CPU just executes code. When the CPU happens to be running OS code, the OS can choose to preempt the current thread. Spin locks do not have any effect on which thread gets preempted, or when, or why.

"Preempt," means that the OS pauses some running thread and allows some other thread to have a turn to run. It can happen on the order of 100 times every second, and usually, none of the threads involved have any awareness of it.

The reason why spin locks have no influence over preemption is, they're just code. A pure spin lock does not call in to the OS or communicate with the OS in any way. The OS has no way to tell the difference between a thread that is calculating digits of pi, or a thread that is balancing bank accounts, or a thread that is waiting for a spin lock.


The SUBTLE magic in the unlock() function consists of memory barrier instructions which are used to enforce the C++ memory model. That's a deep topic— too deep for this answer.


* The C++ Atomic operations library gives you low-level access to those "special" machine instructions.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • Thanks for the answer. Assuming there are 4 threads, 3 of which are spinning in while loop, and 4th is about to call unlock. Inside the unlock() where you said 'SUBTLE magic happens here' , must be the place which takes care of making `is_locked==false` visible to only one thread(out o 3 spinning in while loop), and that is how one thread breaks the while loop. Why other 'MAGIC HAPPENS HERE....' is needed again? Secondly, And also shouldn't is_locked set to true as the first thing after the while loop ends? – anurag86 Apr 29 '20 at 11:08
  • The barrier in the `unlock()` call only has to ensure that other threads, which may be running on different CPUs, will see the changed value of `is_locked`. It's the magic in the `lock()` function that ensures that only one of those other threads threads can 'win' the lock. On many instruction set architectures (ISA), some variant of a [_compare and swap_ instruction](https://en.wikipedia.org/wiki/Compare-and-swap)_ will be used. On a few ISAs, a pair of instructions, [_load linked_ and _store conditional_](https://en.wikipedia.org/wiki/Load-link/store-conditional) are used instead. – Solomon Slow Apr 29 '20 at 12:55