Implement new spin lock
Reviewers: teon.banek, buda Reviewed By: teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1786
This commit is contained in:
parent
a4cce253c0
commit
3209788cd4
@ -14,8 +14,8 @@
|
||||
#include "io/network/epoll.hpp"
|
||||
#include "io/network/socket.hpp"
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
|
||||
namespace communication {
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
#include "io/network/socket.hpp"
|
||||
#include "io/network/stream_buffer.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace communication {
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#include "glog/logging.h"
|
||||
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
/**
|
||||
* A thread-safe ring buffer. Multi-producer, multi-consumer. Producers get
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "query/frontend/stripped.hpp"
|
||||
#include "query/interpret/frame.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/timer.hpp"
|
||||
|
||||
DECLARE_bool(query_cost_planner);
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/hashing/fnv.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
|
||||
// macro for the default implementation of LogicalOperator::Accept
|
||||
// that accepts the visitor and visits it's input_ operator
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include "transactions/commit_log.hpp"
|
||||
#include "transactions/distributed/engine.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace tx {
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "storage/common/locking/lock_status.hpp"
|
||||
#include "storage/common/locking/record_lock.hpp"
|
||||
#include "transactions/type.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace tx {
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "durability/single_node/wal.hpp"
|
||||
#include "transactions/commit_log.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace tx {
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "raft/raft_interface.hpp"
|
||||
#include "transactions/commit_log.hpp"
|
||||
#include "transactions/transaction.hpp"
|
||||
#include "utils/thread/sync.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace tx {
|
||||
|
||||
|
58
src/utils/spin_lock.hpp
Normal file
58
src/utils/spin_lock.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
/// This class is a wrapper around the `pthread_spinlock_t`. It provides a
|
||||
/// generic spin lock. The lock should be used in cases where you know that the
|
||||
/// lock will be contended only for short periods of time. This lock doesn't
|
||||
/// make any kernel calls (like sleep, or context switching) during its wait for
|
||||
/// the lock to be acquired. This property is only useful when the lock will be
|
||||
/// held for short periods of time and you don't want to introduce the extra
|
||||
/// delays of a sleep or context switch. On the assembly level
|
||||
/// `pthread_spinlock_t` is optimized to use less power, reduce branch
|
||||
/// mispredictions, etc... The explanation can be seen here:
|
||||
/// https://stackoverflow.com/questions/26583433/c11-implementation-of-spinlock-using-atomic/29195378#29195378
|
||||
/// https://software.intel.com/en-us/node/524249
|
||||
class SpinLock {
|
||||
public:
|
||||
SpinLock() {
|
||||
// `pthread_spin_init` returns -1 only when there isn't enough memory to
|
||||
// initialize the lock. That should never occur because the
|
||||
// `pthread_spinlock_t` is an `int` and memory isn't allocated by this init.
|
||||
// The message is probably here to suit all other platforms...
|
||||
CHECK(pthread_spin_init(&lock_, PTHREAD_PROCESS_PRIVATE) == 0)
|
||||
<< "Couldn't construct utils::SpinLock!";
|
||||
}
|
||||
|
||||
void lock() {
|
||||
// `pthread_spin_lock` returns -1 only when there is a deadlock detected
|
||||
// (errno EDEADLOCK).
|
||||
CHECK(pthread_spin_lock(&lock_) == 0) << "Couldn't lock utils::SpinLock!";
|
||||
}
|
||||
|
||||
bool try_lock() {
|
||||
// `pthread_spin_trylock` returns -1 only when the lock is already locked
|
||||
// (errno EBUSY).
|
||||
return pthread_spin_trylock(&lock_) == 0;
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
// `pthread_spin_unlock` has no documented error codes that it could return,
|
||||
// so any error is a fatal error.
|
||||
CHECK(pthread_spin_unlock(&lock_) == 0)
|
||||
<< "Couldn't unlock utils::SpinLock!";
|
||||
}
|
||||
|
||||
~SpinLock() {
|
||||
CHECK(pthread_spin_destroy(&lock_) == 0)
|
||||
<< "Couldn't destruct utils::SpinLock!";
|
||||
}
|
||||
|
||||
private:
|
||||
pthread_spinlock_t lock_;
|
||||
};
|
||||
} // namespace utils
|
@ -5,12 +5,11 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
||||
@ -47,59 +46,6 @@ class CasLock {
|
||||
std::atomic<bool> lock_flag;
|
||||
};
|
||||
|
||||
/// Spinlock is used as a locking mechanism based on an atomic flag and waiting
|
||||
/// loops.
|
||||
///
|
||||
/// It uses the CpuRelax "asm pause" command to optimize wasted time while the
|
||||
/// threads are waiting.
|
||||
class SpinLock {
|
||||
public:
|
||||
void lock() { // Before was memory_order_acquire
|
||||
while (lock_flag_.test_and_set()) {
|
||||
CpuRelax();
|
||||
}
|
||||
}
|
||||
// Before was memory_order_release
|
||||
void unlock() { lock_flag_.clear(); }
|
||||
|
||||
bool try_lock() { return !lock_flag_.test_and_set(); }
|
||||
|
||||
private:
|
||||
// guaranteed by standard to be lock free!
|
||||
mutable std::atomic_flag lock_flag_ = ATOMIC_FLAG_INIT;
|
||||
};
|
||||
|
||||
template <size_t microseconds = 250>
|
||||
class TimedSpinLock {
|
||||
public:
|
||||
TimedSpinLock(std::chrono::seconds expiration) : expiration_(expiration) {}
|
||||
|
||||
void lock() {
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
|
||||
auto start = clock::now();
|
||||
|
||||
while (!lock_flag.test_and_set(std::memory_order_acquire)) {
|
||||
// how long have we been locked? if we exceeded the expiration
|
||||
// time, throw an exception and stop being blocked because this
|
||||
// might be a deadlock!
|
||||
|
||||
if (clock::now() - start > expiration_)
|
||||
throw LockTimeoutException("This lock has expired");
|
||||
|
||||
usleep(microseconds);
|
||||
}
|
||||
}
|
||||
|
||||
void unlock() { lock_flag.clear(std::memory_order_release); }
|
||||
|
||||
private:
|
||||
std::chrono::milliseconds expiration_;
|
||||
|
||||
// guaranteed by standard to be lock free!
|
||||
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
|
||||
};
|
||||
|
||||
/// By passing the appropriate parameter to the `RWLock` constructor, it is
|
||||
/// possible to control the behavior of `RWLock` while shared lock is held. If
|
||||
/// the priority is set to `READ`, new shared (read) locks can be obtained even
|
||||
|
Loading…
Reference in New Issue
Block a user