Non-obvious lifetime issue with std::promise and std::future

后端 未结 4 1756
[愿得一人]
[愿得一人] 2021-02-05 18:10

This question is very similar to a previous one here: race-condition in pthread_once()?

It is essentially the same issue - the lifetime of a std::promise en

相关标签:
4条回答
  • 2021-02-05 18:21

    std::promise is just like any other object: you can only access it from one thread at a time. In this case, you are calling set_value() and destroying the object from separate threads without sufficient synchronization: nowhere in the spec does it say that set_value will not touch the promise object after making the future ready.

    However, since this future is used for a one-shot synchronization, you don't need to do that anyway: create the promise/future pair right in run(), and pass the promise to the thread:

    struct synchronous_job
    {
        synchronous_job(std::function<void()> job, dispatcher& d)
            : _job(job)
            , _d(d)
        {
        }
        void run(){
            std::promise<void> p;
            std::future<void> f=p.get_future();
    
            _d.post(
                [&]{
                    cb(std::move(p));
                });
    
            f.wait();
        }
    private:
        void cb(std::promise<void> p)
        {
            _job();
            p.set_value();
        }
        std::function<void()> _job;
        dispatcher&           _d;
    };
    
    0 讨论(0)
  • 2021-02-05 18:22

    In direct answer to your question, the correct answer is to give the std::promise to the thread. That way, it's guaranteed to exist as long as the thread wants it.

    Under the hood, the std::future and std::promise have a shared state that both point to, and is guaranteed to remain available until both sides a destroyed. Conceptually, this is similar to both the promise and the future both having individual copies of a shared_ptr to the same object. This object contains the necessary underlying mechanisms to pass state, block, and other operations.

    As for attempting to signal on destruction, the problem is where would this condition variable exist? The shared area is destroyed once all of the associated futures and promises are destroyed. The deadlock is occurring because the area is being destroyed while it's still being used (because the compiler is unaware another thread is still accessing the promise as it's being destroyed). Adding additional condition variables to any shared state would not help, as they also would be destroyed.

    0 讨论(0)
  • 2021-02-05 18:24

    The canonical answer is to never std::bind to this but rather to a std::weak_ptr. When you get the callback, lock() it and check for NULL before invoking the callback.

    Or, re-stated, never call a member function (from outside) from a scope that doesn't hold a shared_ptr to the object.

    0 讨论(0)
  • 2021-02-05 18:30

    Answering my own question, to offer a workable solution. It doesn't use std::promise or std::future, but it achieves the synchronisation which I'm searching for.

    Update synchronous_job to use a std::condition_variable and std::mutex instead:

    Edit: Updated to include a boolean flag as suggested by Dave S

    struct synchronous_job
    {
        synchronous_job(std::function<void()> job, dispatcher& d)
            : _job(job)
            , _d(d)
            , _done(false)
        {
        }
        void run()
        {
            _d.post(std::bind(&synchronous_job::cb, this));
            std::unique_lock<std::mutex> l(_mtx);
            if (!_done)
                _cnd.wait(l);
        }
    private:
        void cb()
        {
            _job();
            std::unique_lock<std::mutex> l(_mtx);
            _done = true;
            _cnd.notify_all();
        }
        std::function<void()>   _job;
        dispatcher&             _d;
        std::condition_variable _cnd;
        std::mutex              _mtx;
        bool                    _done;
    };
    
    0 讨论(0)
提交回复
热议问题