29 ThreadBlockerWhileUsedFast
No longer used so the code is only recorded here in this web-page.
Provides a signed threadsafe counter that can be incremented and decremented and take on positive or negative values, and allows for waiting until the counter reaches zero.
class ThreadBlockerWhileUsedFast
{
cxNotCloneable(ThreadBlockerWhileUsedFast)
public:
explicit ThreadBlockerWhileUsedFast(int count = 0) : count_(count), blocker_(count > 0 ? 1 : 0) {}
~ThreadBlockerWhileUsedFast()
{
cxAssert(count_ == 0);
}
int operator++()
{
int c = ++count_;
cxAssert(c > 0);
if (c == 1)
{
++blocker_;
}
return c;
}
int operator--()
{
int c = --count_;
cxAssert(c >= 0);
if (c == 0)
{
--blocker_;
}
return c;
}
// Postfix
int operator++(int) { return operator++()-1; }
int operator--(int) { return operator--()+1; }
void Wait()
{
blocker_.Wait();
}
private:
std::atomic<int> count_;
ThreadBlockerUntilZero blocker_;
};
Incorrect version
Consider the following attempt at a fast implementation of ThreadBlockerWhileUsed
class ThreadBlockerWhileUsedFast
{
cxNotCloneable(ThreadBlockerWhileUsedFast)
public:
explicit ThreadBlockerWhileUsedFast(int count = 0) : count_(count), isZero_(count == 0) {}
~ThreadBlockerWhileUsedFast()
{
cxAssert(count_ == 0);
cxAssert(isZero_);
}
int operator++()
{
int c = ++count_;
cxAssert(c > 0);
if (c == 1)
{
std::lock_guard<std::mutex> lock(mutex_);
cxAssert(isZero_);
isZero_ = false;
}
return c;
}
int operator--()
{
int c = --count_;
cxAssert(c >= 0);
if (c == 0)
{
std::lock_guard<std::mutex> lock(mutex_);
cxAssert(!isZero_);
isZero_ = true;
cv_.notify_all();
}
return c;
}
// Postfix
int operator++(int) { return operator++()-1; }
int operator--(int) { return operator--()+1; }
void Wait()
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock,[this]{return isZero_;});
}
private:
std::atomic<int> count_;
std::mutex mutex_;
std::condition_variable cv_; // associated with the condition isZero_
bool isZero_;
};
This implementation is actually incorrect. Consider concurrent calls to operator++() and
operator--().
There is a racing condition for which thread locks the mutex first and this can mean the assertions
cxAssert(isZero_) or cxAssert(!isZero_) may fail.
Nevertheless a fast implementation can be achieved, despite the racing condition. Consider that we introduce a second counter which we increment on each 0-->1 transition, and decrement on each 1-->0 transition of the first counter. At quiescence the second counter is zero if and only if the first counter is zero, independent of the order in which the increment and decrement operations are performed according to the 0-->1 and 1-->0 transitions.