简体   繁体   中英

std::future: Async single producer + any number of consumers problem

I want to model a problem where one asynchronous operation produces some T and then there are multiple calls to get that T on various threads. These calls will be repeated often, so even on one thread, that T will be requested multiple times.

I first thought about using std::promise / std::future for this, ie,

// Declaration
std::promise<T> p;
std::future<T> f = p.get_future();

// Producer code (different function)
p.set_value(...);

// Consumer code (different function; will be called repeatedly from different threads)
return f.get(); 

The problem here is that obivously future::get is a one-shot operation which cannot be repeated, so I thought about shared_future::get() . But shared_future mentions in its documentation:

Access to the same shared state from multiple threads is safe if each thread does it through its own copy of a shared_future object.

This seems weird, as shared_future only has const methods, which should always be thread-safe, so I don't see any necessity for this own copy requirement. I don't know how many threads there will be, so I cannot create one shared_future per thread. The consumer code can be called repeatedly on any thread. What is the best way to do this?

Can I just create a shared_future copy on the fly whenever somebody wants to consume the T ? Ie:

// Declaration
std::promise<T> p;
std::shared_future<T> f = p.get_future();

// Producer code (different function)
p.set_value(...);

// Consumer code (different function; will be called repeatedly from different threads)
std::shared_future<T> fCopy = f; // Make a private copy of f on the stack.
return fCopy.get(); // Use the copy to get the T

Is this how it should be done? Is copy-constructing shared_future thread-safe? Will this have good performance, or should I do it differently?

Apologies for my wrong suggestion in the comment replies, but I think what you want is a std::latch . Initialize to 1, and have anything waiting on it call the wait() method to wait forever until it gets made, or try_wait() if you don't want a forever wait.

Here's where I'm coming from:

int main(int argc, char* argv[])
{
    std::latch mylatch{ 1 };
    std::unique_ptr<int> common_source{};
    std::mutex sync_cout{};

    auto myTester = [&]() {
        mylatch.wait();
        std::lock_guard locker{ sync_cout };
        cout << "Through the latch, value is: " << *common_source << endl;
    };

    // Start the waiting threads
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; i++)
    {
        std::thread mythread{ myTester };
        threads.push_back(std::move(mythread));
    }

    cout << "Threads started" << endl;

    common_source = std::make_unique<int>(15);
    mylatch.count_down();

    std::this_thread::sleep_for(100ms);
    {
        std::lock_guard locker{ sync_cout };
        cout << "After count_down, starting a fresh thread to show it is let through" << endl;
    }
    {
        std::thread mythread{ myTester };
        threads.push_back(std::move(mythread));
    }

    // Wait for the threads to finish
    for (auto& curThread : threads)
    {
        curThread.join();
    }
    cout << "All threads down" << endl;
}

Adapt as you see fit. Change the unique_ptr to whatever type you want to distribute, and obviously lock for copying it, rather than just outputting a number. And if for some reason a "handler" comes in late (after it's already counted down) it's just let through, just as you'd want, rather than waiting forever, as I show in my example.

By default, any function defined in the C++ standard library is considered to be thread-safe relative to some object if that function takes that object by pointer/reference to a const object. shared_future has a copy constructor, and it takes the reference by const . As such, the copy constructor of a shared_future is thread-safe relative to the shared_future it is being copied from, so long as all of the other operations on that shared_future are also thread-safe.

So as long as you don't do anything thread-unsafe to a shared_future instance, you can make a local copy of that instance from any thread.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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