简体   繁体   English

在C ++ 11中等待多个条件变量的最佳方法是什么?

[英]What is the best way to wait on multiple condition variables in C++11?

First a little context : I'm in the process of learning about threading in C++11 and for this purpose, I'm trying to build a small actor class, essentially (I left the exception handling and propagation stuff out) like so: 首先介绍一下上下文 :我正在学习有关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;
    // ...
};

Every actor runs in its own actor_thread , waits on a new incoming message on incoming_msgs and -- when a message arrives -- processes it. 在自己的每一个演员的运行actor_thread ,等待一个新的输入消息的incoming_msgs和-当信息到达-处理它。

The actor_thread is created together with the actor and has to die together with it, which is why I need some kind of interrupt mechanism in the message_queue::wait_and_pop(std::condition_variable interrupt) . actor_thread是与actor一起创建的,必须与之一起死亡,这就是为什么我需要在message_queue::wait_and_pop(std::condition_variable interrupt)某种中断机制的message_queue::wait_and_pop(std::condition_variable interrupt)

Essentially, I require that wait_and_pop blocks until either a) a new message arrives or b) until the interrupt is fired, in which case -- ideally -- an interrupted_exception is to be thrown. 本质上,我要求wait_and_pop阻塞,直到a) message到达或b)直到触发interrupt为止,在这种情况下-理想情况下-将抛出interrupted_exception

The arrival of a new message in the message_queue is presently modeled also by a std::condition_variable new_msg_notification : 新消息在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;
}

To cut the long story short, the question is this: How do I interrupt the waiting for a new message in new_msg_notification.wait(...) when the interrupt is triggered (without introducing a time-out)? 长话短说, 问题是:触发interrupt时(不引入超时),如何在new_msg_notification.wait(...)中断等待新消息?

Alternatively, the question may be read as: How do I wait until any one of two std::condition_variable s are signaled? 另外,该问题也可以理解为:如何等待两个std::condition_variable的任何一个发出信号?

One naive approach seems to be not to use std::condition_variable at all for the interrupt and instead just use an atomic flag std::atomic<bool> interrupted and then busy wait on new_msg_notification with a very small time-out until either a new message has arrived or until true==interrupted . 一种幼稚的方法似乎根本不使用std::condition_variable作为中断,而只是使用原子标志std::atomic<bool> interrupted ,然后忙于等待很小的超时时间new_msg_notification ,直到新的消息已到达或直到true==interrupted However, I would very much like to avoid busy waiting. 但是,我非常想避免繁忙的等待。


EDIT: 编辑:

From the comments and the answer by pilcrow, it looks like there are basically two approaches possible. 从pilcrow的评论和答案看来,基本上可以采用两种方法。

  1. Enqueue a special "Terminate" message, as proposed by Alan, mukunda and pilcrow. 按照Alan,mukunda和pilcrow的建议,加入特殊的“终止”消息。 I decided against this option because I have no idea about the size of the queue at the time I want the actor to terminate. 我决定不使用此选项,因为我不希望在actor终止时队列的大小。 It may very well be (as it is mostly the case when I want something to quickly terminate) that there are thousands of messages left to process in the queue and it seems unacceptable to wait for them to be processed until finally the terminate message gets its turn. 很有可能(在大多数情况下,我想快速终止某件事)队列中还有成千上万的消息要处理,而等待这些消息直到最终获得终止消息之前,似乎无法接受转。
  2. Implement a custom version of a condition variable, that may be interrupted by another thread by forwarding the notification to the condition variable that the first thread is waiting on. 实现条件变量的自定义版本,将通知转发到第一个线程正在等待的条件变量,该变量可能会被另一个线程中断。 I opted for this approach. 我选择了这种方法。

For those of you interested, my implementation goes as follows. 对于您感兴趣的人,我的实现如下。 The condition variable in my case is actually a semaphore (because I like them more and because I liked the exercise of doing so). 在我的情况下,条件变量实际上是一个semaphore (因为我更喜欢它们,并且因为我喜欢这样做)。 I equipped this semaphore with an associated interrupt which can be obtained from the semaphore via semaphore::get_interrupt() . 我为该信号量配备了相关的interrupt ,可以通过semaphore::get_interrupt()从该信号semaphore::get_interrupt()获取该interrupt If now one thread blocks in semaphore::wait() , another thread has the possibility to call semaphore::interrupt::trigger() on the interrupt of the semaphore, causing the first thread to unblock and propagate an interrupt_exception . 如果现在有一个线程在semaphore::wait()semaphore::wait() ,则另一个线程可以在semaphore::interrupt::trigger()上调用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--;
}

Using this semaphore , my message queue implementation now looks like this (using the semaphore instead of the std::condition_variable I could get rid of the std::mutex : 使用这个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(); }
};

My actor , is now able to interrupt its thread with very low latency in its thread. 我的actor现在可以以非常低的延迟中断线程。 The implementation presently like this: 目前的实现是这样的:

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);
    }
};

You ask, 你问,

What is the best way to wait on multiple condition variables in C++11? 在C ++ 11中等待多个条件变量的最佳方法是什么?

You can't, and must redesign. 您不能,必须重新设计。 One thread may wait on only one condition variable (and its associated mutex) at a time. 一个线程一次只能等待一个条件变量(及其关联的互斥体)。 In this regard the Windows facilities for synchronization are rather richer than those of the "POSIX-style" family of synchronization primitives. 在这方面,Windows的同步功能比同步原语的“ POSIX样式”系列的功能更丰富。

The typical approach with thread-safe queues is to enqueue a special "all done!" 使用线程安全队列的典型方法是排队一个特殊的“全部完成!”。 message, or to design a "breakable" (or "shutdown-able") queue. 消息,或设计一个“易碎”(或“可关闭”)队列。 In the latter case, the queue's internal condition variable then protects a complex predicate: either an item is available or the queue has been broken. 在后一种情况下,队列的内部条件变量将保护一个复杂的谓词:某个项目可用队列已损坏。

In a comment you observe that 在评论中,您观察到

a notify_all() will have no effect if there is no one waiting 如果没有人等待,notify_all()将无效

That's true but probably not relevant. 没错,但可能不相关。 wait() ing on a condition variable also implies checking a predicate, and checking it before actually blocking for a notification. 在条件变量上使用wait()还意味着检查谓词,并实际阻塞通知之前对其进行检查。 So, a worker thread busy processing a queue item that "misses" a notify_all() will see, the next time it inspects the queue condition, that the predicate (a new item is available, or, the queue is all done) has changed. 因此,忙于处理“未命中” notify_all()的队列项的工作线程将在下一次检查队列条件时看到谓词(新项可用,或者队列已完成)已更改。

Recently I resolved this issue with the help of single condition variable and separate Boolean variable for each producer/worker. 最近,我借助单个条件变量和每个生产者/工人的单独布尔变量解决了这个问题。 The predicate within the wait function in consumer thread can check for these flags and take the decision which producer/worker has satisfied the condition. 使用者线程中的wait函数中的谓词可以检查这些标志,并确定哪个生产者/工人满足条件。

Maybe this can works: 也许这可以工作:

get rid of interrupt. 摆脱中断。

 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
        }
}

In destructor, replace interrupt.notify_all() with new_msg_notification.notify_all() 在析构函数中,将interrupt.notify_all()替换为new_msg_notification.notify_all()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM