Last active
March 6, 2022 23:28
-
-
Save EvanBalster/abb1f16a48d45d48589ab41231559a75 to your computer and use it in GitHub Desktop.
A tiny non-blocking reference counter for recyclable objects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <atomic> // Requires C++11 or a suitable polyfill | |
/* | |
A reference counting guard for reusable objects. | |
Works similar to the mechanisms of weak_ptr. | |
Any number of accessors may visit() the passage if not closed. | |
After successful entry they should call exit(). | |
*/ | |
struct visitor_guard | |
{ | |
public: | |
using int_t = int; | |
static const int_t | |
flag_open = int_t(1) << (8*sizeof(int_t) - 2), | |
flag_locked = int_t(3) << (8*sizeof(int_t) - 2); | |
static_assert(flag_locked < 0); | |
static_assert(flag_locked & flag_open); | |
static_assert((flag_locked + 0xFFFF) & flag_open); | |
public: | |
visitor_guard(bool start_open = true) : _x(start_open ? flag_open : 0 ) {} | |
// Try to enter the passage, returning whether successful. | |
// visit() succeeds if the passage is open. | |
// enter() succeeds if the passage is open or closed (not locked). | |
// join () succeeds if the passage is open or not vacant. | |
// Call leave() following a success to avoid starving lock(). | |
bool visit() noexcept {if (_x.fetch_add(1)>=flag_open) {return 1;} else {leave(); return 0;}} | |
bool join () noexcept {if (_x.fetch_add(1)>=1) {return 1;} else {leave(); return 0;}} | |
bool enter() noexcept {if (_x.fetch_add(1)>=0) {return 1;} else {leave(); return 0;}} | |
void leave() noexcept {_x.fetch_sub(1);} | |
// Close or reopen this passage. | |
void close () noexcept {_x.fetch_and(~flag_open);} | |
bool reopen() noexcept {return _x.fetch_or(flag_open) > 0;} | |
// Lock up the passage | |
// lock() will fail if the passage is open OR has visitors. | |
// Call unlock(), and perhaps reopen(), to re-enable entry. | |
bool try_lock() noexcept {int r = 0; return _x.compare_exchange_strong(r, flag_locked);} | |
void unlock() noexcept {_x.fetch_and(~flag_locked);} | |
// These functions may be used to observe state, | |
// But are unsuitable for synchronizing resource access. | |
bool is_open () const noexcept {return _x.load(std::memory_order_relaxed) >= flag_open;} | |
bool is_closed() const noexcept {return _x.load(std::memory_order_relaxed) < flag_open;} | |
bool is_locked() const noexcept {return _x.load(std::memory_order_relaxed) < 0;} | |
int_t visitors() const noexcept {return _x.load(std::memory_order_relaxed) & ~flag_open;} | |
bool is_vacant() const noexcept {return (_x.load(std::memory_order_relaxed)|flag_open) == flag_open;} | |
bool can_lock () const noexcept {return _x.load(std::memory_order_relaxed) == 0;} | |
private: | |
// Raw value of this primitive. Don't touch it. | |
std::atomic<int_t> _x; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment