首先是一个上下文:我正在学习C ++ 11中的线程,为此,我正在尝试构建一个小的actor
类,本质上(我离开了异常处理和传播的东西),如下所示:
class actor {
private: std::atomic<bool> stop;
private: std::condition_variable interrupt;
private: std::thread actor_thread;
private: message_queue incoming_msgs;
public: actor()
: stop(false),
actor_thread([&]{ run_actor(); })
{}
public: virtual ~actor() {
// if the actor is destroyed, we must ensure the thread dies too
stop = true;
// to this end, we have to interrupt the actor thread which is most probably
// waiting on the incoming_msgs queue:
interrupt.notify_all();
actor_thread.join();
}
private: virtual void run_actor() {
try {
while(!stop)
// wait for new message and process it
// but interrupt the waiting process if interrupt is signaled:
process(incoming_msgs.wait_and_pop(interrupt));
}
catch(interrupted_exception) {
// ...
}
};
private: virtual void process(const message&) = 0;
// ...
};
每个actor都在自己的actor_thread
中运行,等待incoming_msgs
上的新传入消息,并且 - 当消息到达时 - 处理它。
actor_thread
与actor
一起创建,并且必须与它一起死亡,这就是我需要message_queue::wait_and_pop(std::condition_variable interrupt)
中某种中断机制的原因。
基本上,我需要wait_and_pop
阻止,直到任何一个
a)新的message
到达或
b)直到interrupt
被解雇,在这种情况下 - 理想情况下 - 要抛出interrupted_exception
。
message_queue
中新消息的到达目前也由std::condition_variable new_msg_notification
建模:
// ...
// in class message_queue:
message wait_and_pop(std::condition_variable& interrupt) {
std::unique_lock<std::mutex> lock(mutex);
// How to interrupt the following, when interrupt fires??
new_msg_notification.wait(lock,[&]{
return !queue.empty();
});
auto msg(std::move(queue.front()));
queue.pop();
return msg;
}
简而言之,问题是这样的:当new_msg_notification.wait(...)
被触发时,如何在interrupt
中断等待新消息(不引入)超时)?
或者,问题可能理解为:我如何等到两个std::condition_variable
中的任何一个发出信号?
一种天真的方法似乎是根本不使用std::condition_variable
来代替中断而只是使用原子标记std::atomic<bool> interrupted
然后在new_msg_notification
上忙着等待很短的时间 - 直到新消息到达或直到true==interrupted
为止。但是,我非常希望避免忙碌等待。
根据评论和pilcrow的答案,看起来基本上有两种可能的方法。
对于那些感兴趣的人,我的实现如下。在我的情况下,条件变量实际上是semaphore
(因为我更喜欢它们,因为我喜欢这样做的练习)。我为此信号量配备了相关的interrupt
,可以通过semaphore::get_interrupt()
从信号量中获取。如果现在一个线程在semaphore::wait()
中阻塞,则另一个线程可以在信号量中断上调用semaphore::interrupt::trigger()
,导致第一个线程解除阻塞并传播interrupt_exception
。
struct
interrupt_exception {};
class
semaphore {
public: class interrupt;
private: mutable std::mutex mutex;
// must be declared after our mutex due to construction order!
private: interrupt* informed_by;
private: std::atomic<long> counter;
private: std::condition_variable cond;
public:
semaphore();
public:
~semaphore() throw();
public: void
wait();
public: interrupt&
get_interrupt() const { return *informed_by; }
public: void
post() {
std::lock_guard<std::mutex> lock(mutex);
counter++;
cond.notify_one(); // never throws
}
public: unsigned long
load () const {
return counter.load();
}
};
class
semaphore::interrupt {
private: semaphore *forward_posts_to;
private: std::atomic<bool> triggered;
public:
interrupt(semaphore *forward_posts_to) : triggered(false), forward_posts_to(forward_posts_to) {
assert(forward_posts_to);
std::lock_guard<std::mutex> lock(forward_posts_to->mutex);
forward_posts_to->informed_by = this;
}
public: void
trigger() {
assert(forward_posts_to);
std::lock_guard<std::mutex>(forward_posts_to->mutex);
triggered = true;
forward_posts_to->cond.notify_one(); // never throws
}
public: bool
is_triggered () const throw() {
return triggered.load();
}
public: void
reset () throw() {
return triggered.store(false);
}
};
semaphore::semaphore() : counter(0L), informed_by(new interrupt(this)) {}
// must be declared here because otherwise semaphore::interrupt is an incomplete type
semaphore::~semaphore() throw() {
delete informed_by;
}
void
semaphore::wait() {
std::unique_lock<std::mutex> lock(mutex);
if(0L==counter) {
cond.wait(lock,[&]{
if(informed_by->is_triggered())
throw interrupt_exception();
return counter>0;
});
}
counter--;
}
使用此semaphore
,我的消息队列实现现在看起来像这样(使用信号量而不是std::condition_variable
我可以摆脱std::mutex
:
class
message_queue {
private: std::queue<message> queue;
private: semaphore new_msg_notification;
public: void
push(message&& msg) {
queue.push(std::move(msg));
new_msg_notification.post();
}
public: const message
wait_and_pop() {
new_msg_notification.wait();
auto msg(std::move(queue.front()));
queue.pop();
return msg;
}
public: semaphore::interrupt&
get_interrupt() const { return new_msg_notification.get_interrupt(); }
};
我的actor
现在能够在其线程中以非常低的延迟中断其线程。目前的实施是这样的:
class
actor {
private: message_queue
incoming_msgs;
/// must be declared after incoming_msgs due to construction order!
private: semaphore::interrupt&
interrupt;
private: std::thread
my_thread;
private: std::exception_ptr
exception;
public:
actor()
: interrupt(incoming_msgs.get_interrupt()), my_thread(
[&]{
try {
run_actor();
}
catch(...) {
exception = std::current_exception();
}
})
{}
private: virtual void
run_actor() {
while(!interrupt.is_triggered())
process(incoming_msgs.wait_and_pop());
};
private: virtual void
process(const message&) = 0;
public: void
notify(message&& msg_in) {
incoming_msgs.push(std::forward<message>(msg_in));
}
public: virtual
~actor() throw (interrupt_exception) {
interrupt.trigger();
my_thread.join();
if(exception)
std::rethrow_exception(exception);
}
};
答案 0 :(得分:14)
你问,
在C ++ 11中等待多个条件变量的最佳方法是什么?
你不能,而且必须重新设计。一个线程一次只能等待一个条件变量(及其关联的互斥锁)。在这方面,用于同步的Windows设备比那些&#34; POSIX-style&#34;同步原语族。
使用线程安全队列的典型方法是将一个特殊的&#34;所有内容排入队列!&#34;消息,或设计一个&#34;易碎的&#34; (或&#34; shutdown-able&#34;)队列。在后一种情况下,队列的内部条件变量然后保护复杂谓词:项目可用或队列已被破坏。
在评论中你会发现
如果没有人等待,则notify_all()将无效
这是真的但可能不相关。 wait()
条件变量还意味着检查谓词,并在实际阻止通知之前检查。因此,工作线程忙于处理一个&#34;错过&#34; notify_all()
下次检查队列条件时,会看到谓词(新项目可用,或队列全部完成)已更改。
答案 1 :(得分:3)
最近,我借助单个条件变量和每个生产者/工人的单独布尔变量解决了这个问题。 消费者线程中的wait函数中的谓词可以检查这些标志,并决定哪个生产者/工人满足条件。
答案 2 :(得分:0)
也许这可行:
摆脱中断。
message wait_and_pop(std::condition_variable& interrupt) {
std::unique_lock<std::mutex> lock(mutex);
{
new_msg_notification.wait(lock,[&]{
return !queue.empty() || stop;
});
if( !stop )
{
auto msg(std::move(queue.front()));
queue.pop();
return msg;
}
else
{
return NULL; //or some 'terminate' message
}
}
在析构函数中,将interrupt.notify_all()
替换为new_msg_notification.notify_all()