Program Listing for File spinlock.hpp

Return to documentation for file (include/wrench/multithreading/spinlock.hpp)

//==--- wrench/multithreading/spinlock.hpp ------------------ -*- C++ -*----==//
//
//                                  Wrench
//
//                      Copyright (c) 2020 Rob Clucas
//
//  This file is distributed under the MIT License. See LICENSE for details.
//
//==------------------------------------------------------------------------==//
//
//
//==------------------------------------------------------------------------==//

#ifndef WRENCH_MULTITHREADING_SPINLOCK_HPP
#define WRENCH_MULTITHREADING_SPINLOCK_HPP

#include <chrono>
#include <thread>

namespace wrench {

// A small spinlock implementation.
struct Spinlock {
 private:
  // clang-format off
  static constexpr uint8_t unlocked = 0;
  static constexpr uint8_t locked   = 1;
  // clang-format

  struct Sleeper {
    static constexpr uint32_t max_spins = 2000;

    static inline auto sleep() noexcept -> void {
      using namespace std::chrono_literals;
      std::this_thread::sleep_for(200us);
    }

    auto wait() noexcept -> void {
      if (spincount_ < max_spins) {
        spincount_++;
        // Essentially _mm_pause() and a memory barrier in one instruction.
        // Just to make sure that there is no memory reordering which might
        // be the case if the compiler decided to move things around.
        // The pause prevents speculative loads from causing pipeline clears
        // due to memory ordering mis-speculation.
        asm volatile("pause" ::: "memory");
        return;
      }
      sleep();
    }

   private:
    uint32_t spincount_ = 0;
  };

 public:
  auto try_lock() noexcept -> bool {
    return __sync_bool_compare_and_swap(&lock_, unlocked, locked);
  }

  auto lock() noexcept -> void {
    Sleeper sleeper;
    while (!__sync_bool_compare_and_swap(&lock_, unlocked, locked)) {
      do {
        // Wait until a cas might succeed:
        sleeper.wait();
      } while (lock_);
    }
  }

  auto unlock() noexcept -> void {
    // Memory barries so that we can write the lock to unlocked.
    asm volatile("" ::: "memory");
    lock_ = unlocked;
  }

 private:
  uint8_t lock_ = unlocked;
};

} // namespace wrench

#endif // WRENCH_MULTITHREADING_SPINLOCK_HPP