В большинстве случаев проще всего использовать порядок, чтобы внутри вашего объекта были мьютексы и игнорировать атомистику.Просто убедитесь, что все обращения к вашим данным защищены блокировкой.
Если вы сохраняете только один бит, возможно, что быстрое выполнение set
и reset
приведет к потере пробужденийожидание потоков будет запланировано только после завершения reset
.Для решения проблемы я буду использовать счетчик.Самый низкий бит счетчика - это его «открытое» состояние.Каждое изменение этого состояния осуществляется с приращением.Я использую 64-битный счетчик на всякий случай.Крайне маловероятно, что 32-разрядных битов будет недостаточно, даже если это может привести к циклическому изменению во время длительной работы программы.
class manual_reset_event
{
public:
void wait_one()
{
std::unique_lock<std::mutex> lock(mutex_);
uint64_t initial_value = value_;
if(initial_value & 1)
{
return;
}
while (value_ == initial_value)
{
signalled_.wait(lock);
}
}
void set()
{
std::unique_lock<std::mutex> lock(mutex);
if((value_ & 1) == 0)
{
value_++;
lock.release(); // optimization
signalled_.notify_all();
}
}
void reset()
{
std::unique_lock<std::mutex> lock(mutex);
if(value_ & 1)
{
value_++;
}
}
private:
std::mutex mutex_;
std::condition_variable signalled_;
uint64_t value_;
};
Если вы настаиваете на том, чтобы избежать ненужного использования блокировки, вы можете использовать атомарность, норешение несколько сложнее, так как есть еще много вариантов для рассмотрения.
class manual_reset_event
{
public:
void wait_one()
{
uint64_t initial_value = value_;
if(initial_value & 1)
{
return;
}
std::unique_lock<std::mutex> lock(mutex_);
while (value_ == initial_value)
{ // !
signalled_.wait(lock);
}
}
void set()
{
uint64_t initial_value = value_;
if(initial_value & 1)
{
return;
}
std::unique_lock<std::mutex> lock(mutex_);
// Still need lock to prevent lost wakeup if atomic change happens when
// other thread is on "// !" line.
if(value.compare_exchange_strong(initial_value, initial_value + 1)) {
// One strong attempt is enough. If it fails than someone else must have
// succeeded. It's as if these two set() operations happened at the same time.
lock.release();
signalled_.notify_all();
}
}
void reset()
{
uint64_t initial_value = value_;
if((initial_value & 1) == 0)
{
return;
}
std::unique_lock<std::mutex> lock(mutex_);
value.compare_exchange_strong(initial_value, initial_value + 1);
}
private:
std::mutex mutex_;
std::condition_variable signalled_;
std::atomic<uint64_t> value_;
};