I know that using atomics is dangerous (I watched Herb Sutter's 3hr lecture a few days ago), but the following use case seems reasonable to me, in terms of being simple and well contained.
Questions: (a) Is there something wrong with this? (Surely, there must be.) (b) Is there a name for this kind of hybrid atomic/mutex based approach? (c) Is there a simpler way of achieving the same thing?
The goal is to have a thread-safe counter class which we can call attempt_invalidation() on, knowing that it will only set its invalid flag to true if the count is at zero. There will be no other public methods on the class, but we will have a friend class specially designed to do RAII incrementing/decrementing of the counter.
class hybrid_counter{
friend class hybrid_counter_user;
bool invalidated = false;
int counter_a = 0;
std::atomic_int counter_b;
std::mutex mu;
void increment_safely(){
std::lock_guard<std::mutex> gaurd(mu);
if ( !invalidated )
counter_a++;
};
void increment_dangerously(){
counter_b++;
};
void decrement(){
counter_b--;
};
public:
bool attempt_invalidation(){
if(counter_a + counter_b == 0){
std::lock_guard<std::mutex> gaurd(mu);
if(counter_a + counter_b == 0)
invalidated = true;
}
return invalidated;
};
};
This is the friend class that knows how to use the counter correctly:
class hybrid_counter_user{
public:
hybrid_counter_user(hybrid_counter& hc){
hc.increment_safely();
c = &hc;
};
~hybrid_counter_user(){
if(c)
c->decrement();
};
hybrid_counter_user(hybrid_counter_user&& old){
c = old.c;
old.c = nullptr;
}
hybrid_counter_user(hybrid_counter_user& other){
c = other.c;
c->increment_dangerously();
}
private:
hybrid_counter* c;
};
Note that the copy constructor uses the fact that hybrid_counter remains valid while other is in scope and other's destructor cannot be reordered with increment_dangerously because both involve the same atomic var. The move constructor is simply transferring responsibility for decrementing.
Aucun commentaire:
Enregistrer un commentaire