Created
December 19, 2012 05:22
-
-
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…
This file contains hidden or 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_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; | |
| } | |
| } |
This file contains hidden or 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
| #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 |
This file contains hidden or 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 "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(); | |
| } |
This file contains hidden or 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
| #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 |
This file contains hidden or 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 "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