DEMYSTIFYING SYNCHRONIZATION

Justas Masiulis 2019

Alternative presentiation names I wanted to steal from Herb Sutter

Juggling Razor Blades

Atomic Weapons

A necessary note

Threads are the lowest level construct

There are many other concurrency models
  • Actors
  • Coroutines
  • Futures

So what makes up the simplest lock


class naive_prototype_lock {
  bool _locked{ false };

public:
  void lock() {
    while(!_locked) { /* do nothing */ }
    _locked = true;
  }
  void unlock() {
	_locked = false;
  }
};

class atomic_bool_lock {
  std::atomic<bool> _locked{ false };
	
public:
  void lock() {
    bool expected;
    do {
      expected = false;
    } while(!_locked.compare_exchange_weak(expected, true));
  }
  void unlock() {
    _locked.store(false);
  }
};
							

class atomic_flag_lock {
  std::atomic_flag _locked{ ATOMIC_FLAG_INIT };
	
public:
  void lock() {
    while(_locked.test_and_set(std::memory_order_acquire))
      ;
  }
  void unlock() {
    _locked.clear(std::memory_order_release);
  }
};
							

This looks relatively simple, what's the catch?

Nothing's perfect


Pros

  • Really fast to lock and unlock

Cons

  • Can consume whole 100% of CPU while spinning

What's the alternative?

We can ask help from the OS

The thread can be suspended by the scheduler

What windows offers


NTSTATUS NtAlertThreadByThreadId(HANDLE ThreadId);
								
NTSTATUS NtWaitForAlertByThreadId(
	void* Address, LARGE_INTEGER* Timeout);
						

Tradeoffs

  • Locking and unlocking is very slow
  • Waiting is essentially free
  • Added complexity of code

What does this mean?

Pretty much any generic lock will be a hybrid

They will spin for some time and then ask OS to put them to sleep

A challenge for the end


while (true) {
  // we are in intermediate state and processing is being done
  const auto cur = _refcount.load();
  if (cur == intermediate_state)
    continue;

  // if the refcount is 0 set it to intermediate state
  Counter expected = 0;
  if (_refcount.compare_exchange_strong(
	  expected, intermediate_state)) {
    // SOME REDACTED WORK HAPPENS HERE
	
    // we are done and can set the refcount to 1
    _refcount.store(1);
    break;
  }
  // attempt to increment the refcount whose state we got from last check
  else if (_refcount.compare_exchange_strong(
	  expected, expected + 1))
    break;
}
							

Thank you for your attention

Challenge answer


else if (expected != intermediate_state &&
	_refcount.compare_exchange_strong(expected, expected + 1))