Skip to content

Instantly share code, notes, and snippets.

@wilkie
Created December 19, 2012 05:22
Show Gist options
  • Select an option

  • Save wilkie/4334571 to your computer and use it in GitHub Desktop.

Select an option

Save wilkie/4334571 to your computer and use it in GitHub Desktop.
A lock-free reference counter. These two classes offer an easy method of ensuring copies of classes with data that must be reclaimed can only be reclaimed when all other copies are destroyed. It uses an atomic compare and exchange method of managing a counter without using an explicit mutex, so it is thread safe. I didn't want to use a smart poi…
#include "atomic_counter.hpp"
AtomicCounter::AtomicCounter() {
AtomicCounter(0);
}
AtomicCounter::AtomicCounter(unsigned int value) :
_value(value) {
}
unsigned int AtomicCounter::increment() {
return add((unsigned int)1);
}
unsigned int AtomicCounter::decrement() {
return add((unsigned int)-1);
}
unsigned int AtomicCounter::add(unsigned int value) {
unsigned int oldVal;
unsigned int newVal;
for (;;) {
oldVal = _value;
newVal = _value + value;
if (_compareExchange(&_value, oldVal, newVal)) {
return newVal;
}
}
}
unsigned int AtomicCounter::value() const {
return _value;
}
// TODO: Maybe do something with a mutex when architectures don't support things.
bool AtomicCounter::_compareExchange(unsigned int* reference, unsigned int compare, unsigned int exchange) {
__asm {
// Stack:
// | old
// |RA |ebp|ref|cmp|exc|
// EBP +4 +8 +12 +16
mov ECX, [EBP+16]; // assign ECX to exchange
mov EAX, [EBP+12]; // assign accumulator to compare
mov EDX, [EBP+8]; // get pointer to reference
lock cmpxchg [EDX], ECX;
// Afterward, EAX will still be compare if the update occurred
// otherwise, EAX will be the reference value.
// ZF is cleared when exchange occurs
// Therefore set EAX to be 0 when the update did not occur, 1 otherwise
setz AL;
}
}
#ifndef ATOMIC_COUNTER_HPP
#define ATOMIC_COUNTER_HPP
class AtomicCounter {
public:
/*
* Constructs a 32-bit unsigned counter whose operations are guaranteed to be atomic.
*/
AtomicCounter();
AtomicCounter(unsigned int value);
/*
* Atomically increments the counter.
*/
unsigned int increment();
/*
* Atomically decrements the counter.
*/
unsigned int decrement();
/*
* Adds the given amount to the counter.
*/
unsigned int add(unsigned int value);
/*
* Gives the value of the counter.
*/
unsigned int value() const;
private:
bool _compareExchange(unsigned int* reference, unsigned int compare, unsigned int exchange);
unsigned int _value;
};
#endif
#include "reference_counter.hpp"
ReferenceCounter::ReferenceCounter() {
_counter = new AtomicCounter(1);
}
ReferenceCounter::ReferenceCounter(const ReferenceCounter& b) :
_counter(b._counter) {
_counter->increment();
}
ReferenceCounter::~ReferenceCounter() {
if (isAlone()) {
delete _counter;
}
else {
_counter->decrement();
}
}
bool ReferenceCounter::isAlone() const {
return _counter->value() == 1;
}
unsigned int ReferenceCounter::value() const {
return _counter->value();
}
#ifndef REFERENCE_COUNTER_HPP
#define REFERENCE_COUNTER_HPP
#include "atomic_counter.hpp"
class ReferenceCounter {
public:
/*
* Constructs a reference counter that keeps track of how many times it
* is copied and destroyed.
*/
ReferenceCounter();
ReferenceCounter(const ReferenceCounter&);
~ReferenceCounter();
/*
* Are we the only one?
*/
bool isAlone() const;
/*
* Gives the value of the counter.
*/
unsigned int value() const;
private:
// Do not allow this; it isn't useful in the intended usecase anyway.
ReferenceCounter& operator= (const ReferenceCounter&);
AtomicCounter* _counter;
};
#endif
#include "reference_counter.hpp"
// The Thing class will only run the destructor logic when all Thing copies are cleaned up
class Thing {
public:
Thing() {
_data = new int[1024];
}
// Default copy constructor works as is.
~Thing() {
if (_counter.isAlone()) {
delete _data;
}
}
private:
int* _data;
// All we need is to define a ReferenceCounter and the rest will just work.
ReferenceCounter _counter;
}
void foo(Thing x) {
Thing y = x;
// It gets copied somewhere that persists
}
Thing a = Thing();
// pass a somewhere, it gets copied
foo(a);
// Thing a can be released, yet destructor doesn't function until all of the copies are out of scope.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment