ManualResetEvent(从C#)在C+上的实现+;:如何避免比赛状态 我想了解更多关于多线程编程的知识,我认为在C++中实现一些C同步基本图元是一个很好的练习。 我已经开始了,到目前为止我已经做到了: class manual_reset_event { public: void wait_one() { if (cv_flag_.load() == false) { thread_local std::mutex mutex; std::unique_lock<std::mutex> lock(mutex); cond_var_.wait(lock, [this]() { return cv_flag_.load(); }); } } void set() { cv_flag_.store(true); cond_var_.notify_all(); } void reset() { cv_flag_.store(false); } private: std::condition_variable cond_var_; std::atomic<bool> cv_flag_; }; 类手动重置事件 { 公众: void wait_one() { 如果(cv_标志加载()==false) { 线程\本地标准::互斥互斥; std::唯一锁(互斥锁); cond_var_uu.wait(lock,[this](){return cv_uflag_uu.load();}); } } 空集() { cv_标志_.存储(真); 条件变量通知所有(); } 无效重置() { cv_标志_.存储(假); } 私人: std::条件变量cond\u var; 标准::原子cv_标志u; };
但是,这里有一个竞争条件:可以在一个线程上调用wait_one(),通过if(cv_标志)检查,然后从另一个线程调用set。这将导致wait_one()等待,即使cv_flag_uu现在为true。ManualResetEvent(从C#)在C+上的实现+;:如何避免比赛状态 我想了解更多关于多线程编程的知识,我认为在C++中实现一些C同步基本图元是一个很好的练习。 我已经开始了,到目前为止我已经做到了: class manual_reset_event { public: void wait_one() { if (cv_flag_.load() == false) { thread_local std::mutex mutex; std::unique_lock<std::mutex> lock(mutex); cond_var_.wait(lock, [this]() { return cv_flag_.load(); }); } } void set() { cv_flag_.store(true); cond_var_.notify_all(); } void reset() { cv_flag_.store(false); } private: std::condition_variable cond_var_; std::atomic<bool> cv_flag_; }; 类手动重置事件 { 公众: void wait_one() { 如果(cv_标志加载()==false) { 线程\本地标准::互斥互斥; std::唯一锁(互斥锁); cond_var_uu.wait(lock,[this](){return cv_uflag_uu.load();}); } } 空集() { cv_标志_.存储(真); 条件变量通知所有(); } 无效重置() { cv_标志_.存储(假); } 私人: std::条件变量cond\u var; 标准::原子cv_标志u; };,c++,multithreading,c++11,C++,Multithreading,C++11,但是,这里有一个竞争条件:可以在一个线程上调用wait_one(),通过if(cv_标志)检查,然后从另一个线程调用set。这将导致wait_one()等待,即使cv_flag_uu现在为true。 我可以通过在等待、设置和重置上使用锁来解决这个问题。 我想我也可以通过在wait_one()上的cond_var.wait()之后立即调用cond_var.notify_all()来解决这个问题,但我认为这不是一个好主意(尽管可能我错了)。 我想知道是否还有其他方法(甚至可能是完全不同的方法,不使用
我可以通过在等待、设置和重置上使用锁来解决这个问题。
我想我也可以通过在wait_one()上的cond_var.wait()之后立即调用cond_var.notify_all()来解决这个问题,但我认为这不是一个好主意(尽管可能我错了)。
我想知道是否还有其他方法(甚至可能是完全不同的方法,不使用条件变量)可以在这里避免这种竞争条件。在大多数情况下,最简单的方法是使用顺序将对象内部与互斥体联系起来,而忽略原子。只需确保对数据的所有访问都受到锁的保护 如果只存储一个位,则在随后快速执行
设置
和重置
时,可能会导致唤醒丢失,因为等待的线程只有在重置
完成后才会安排。为了解决这个问题,我将使用计数器。计数器的最低位是其“打开”状态。该状态的每一次更改都以增量的形式实现。我使用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_;
};
类手动重置事件
{
公众:
void wait_one()
{
std::唯一锁(互斥锁);
uint64\u t初始值=值;
if(初始值&1)
{
返回;
}
while(值=初始值)
{
信号等待(锁定);
}
}
空集()
{
std::唯一锁(互斥锁);
如果((值_&1)==0)
{
值uz++;
lock.release();//优化
发出信号通知所有人();
}
}
无效重置()
{
std::唯一锁(互斥锁);
如果(值u1)
{
值uz++;
}
}
私人:
std::mutex mutex;
std::条件变量信号;
uint64_t值;
};
如果你坚持避免不必要的锁使用,你可以使用原子,但是解决方案有些棘手,因为有更多的顺序要考虑。
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_;
};
类手动重置事件
{
公众:
void wait_one()
{
uint64\u t初始值=值;
if(初始值&1)
{
返回;
}
std::唯一锁(互斥锁);
while(值=初始值)
{ // !
信号等待(锁定);
}
}
空集()
{
uint64\u t初始值=值;
if(初始值&1)
{
返回;
}
std::唯一锁(互斥锁);
//仍然需要锁定,以防止在发生原子更改时丢失唤醒
//其他线程位于“/!”行上。
if(值比较交换强(初始值,初始值+1)){
//一次强有力的尝试就足够了。如果失败了,那一定是别人干的
//成功。就好像这两个set()操作同时发生一样。
锁定。释放();
发出信号通知所有人();
}
}
无效重置()
{
uint64\u t初始值=值;
如果((初始_值&1)==0)
{
返回;
}
std::唯一锁(互斥锁);
值。比较交换强(初始值,初始值+1);
}
私人:
std::mutex mutex;
std::条件变量信号;
std::原子值;
};
一个可能的实现-保留等待事件线程的列表。对于手动重置\u事件的保护状态,可以使用std::mutex
。当线程开始等待时,他检查事件的状态,若并没有发出信号,则在列表中插入self“wait block”。这是在受公共对象互斥体保护的“临界区”内完成的。然后,若我们需要等待事件(当且仅当我们插入self-to-wait列表时)-开始等待等待块。但在此之前从“关键部分”退出非常重要,甚至在等待结束后也不会临时获得它。另一方面,线程设置等待线程的事件-获取列表,然后通知所有等待线程(在退出关键部分后),或者可能只通知单个等待线程,首先开始等待。因此,我们可以实现手动重新发送事件逻辑(当所有等待的线程同时唤醒时)或自动重置事件逻辑-当只有一个线程将被唤醒时,事件将再次重置(实际上根本没有设置为信号状态。仅当不再有等待的线程时-事件发出信号时)
class manual_reset_event : std::mutex
{
struct WaitBlock : public std::condition_variable, std::mutex
{
WaitBlock(WaitBlock* next) : next(next), signaled(false) {}
WaitBlock* next;
volatile bool signaled;
void Wait()
{
// synchronization point with Wake()
std::unique_lock<std::mutex> lock(*this);
while (!signaled)
{
// notify_one() yet not called
wait(lock);
}
}
void Wake()
{
{
// synchronization point with Wait()
std::lock_guard<std::mutex> lock(*this);
signaled = true;
}
notify_one();
}
};
WaitBlock* _head;
volatile bool _signaled;
public:
manual_reset_event(bool signaled = false) : _signaled(signaled), _head(0) { }
void wait()
{
lock();//++ protect object state
WaitBlock wb(_head);
bool inserted = false;
if (!_signaled)
{
_head = &wb;
inserted = true;
}
unlock();//-- protect object state
if (inserted)
{
wb.Wait();
}
}
// manual reset logic
void set_all()
{
WaitBlock* last, *head = 0;
lock();//++ protect object state
head = _head, _signaled = true;
unlock();//-- protect object state
while (last = head)
{
head = head->next;
last->Wake();
}
}
// auto reset logic - only one thread will be signaled, event auto reset
void set_single()
{
WaitBlock* last = 0;
lock();//++ protect object state
if (!_signaled)
{
if (last = _head)
{
// wake first waiting thread
WaitBlock* prev = 0, *pwb;
while (pwb = last->next)
{
prev = last, last = pwb;
}
(prev ? prev->next : _head) = 0;
}
else
{
// nobody wait
_signaled = true;
}
}
unlock();//-- protect object state
if (last)
{
last->Wake();
}
}
void reset()
{
_signaled = false;
}
};
类手动重置事件:std::mutex
{
结构WaitBlock:public std::condition_变量,std::mutex
{
WaitBlock(WaitBlock*next):next(next),有信号(false){}
WaitBlock*下一步;
挥发性bool信号;
无效等待()
{
//与Wake()的同步点
std::唯一锁(*此);
当(!发出信号)
{
//notify_one()尚未调用