Я пытаюсь установить некоторую эвристику, чтобы помочь мне выбрать подходящий std::thread
класс для использования.
Насколько я понимаю, от самого высокого уровня (самого простого в использовании, но наименее гибкого) до самого низкого уровня мы имеем:
Я думаю, что у меня есть приличное понимание когда использовать первые два, но до сих пор неясно, std::promise
,
std::future
в сочетании с std::async
вызов, эффективно преобразует производящий обратный вызов / функтор / лямбда в асинхронный вызов (который сразу возвращается по определению). Единственный потребитель может позвонить std::future::get()
блокирующий вызов, чтобы получить свои результаты обратно.
std::shared_future
это просто версия, которая позволяет нескольким потребителям.
Если вы хотите связать std::future
значение с обратным вызовом производителя, но хотите отложить фактический вызов на более позднее время (когда вы связываете задачу с порождающим потоком), std::packaged_task
это правильный выбор. Но теперь, так как соответствующие std::future
к std::package_task
может, в общем случае, быть доступным для нескольких потоков, возможно, нам придется позаботиться об использовании std::mutex
, Обратите внимание, что с std::async
В первом случае нам не нужно беспокоиться о блокировке.
Прочитав некоторые интересные ссылки на обещание, Я думаю, что понимаю его механизмы и как их настроить, но мой вопрос, когда вы решите использовать обещание над остальными тремя?
Я больше ищу ответ на уровне приложения, например практическое правило (заполните ??? в 3. выше), в отличие от ответа в ссылке (например, используйте std :: обещать реализовать некоторую библиотеку механизм), поэтому я могу более легко объяснить, как выбрать подходящий класс для начинающего пользователя std::thread
,
Другими словами, было бы неплохо иметь полезный пример того, что я могу сделать с std::promise
тот не могу быть сделано с другими механизмами.
ОТВЕТ
std::future
странный зверь: в общем, вы не можете изменить его значение напрямую.
Три производителя, которые могут изменить его стоимость:
std::async
через асинхронный обратный вызов, который вернет std::future
пример.std::packaged_task
, который при передаче потоку вызывает его обратный вызов, тем самым обновляя std::future
экземпляр, связанный с этим std::packaged_task
, Этот механизм позволяет раннее связывание производителя, но более поздний вызов.std::promise
, что позволяет изменить его связанный std::future
через его set_value()
вызов. С этим прямым контролем над мутацией std::future
, мы должны убедиться, что дизайн является потокобезопасным, если есть несколько производителей (используйте std::mutex
по мере необходимости).Я думаю Сет Карнеги ответ:
Простой способ думать о том, что вы можете установить будущее
возвращая значение или используя обещание. будущее не имеет заданного метода;
эта функциональность обеспечивается обещанием.
помогает уточнить, когда использовать обещание. Но мы должны помнить, что std::mutex
может потребоваться, так как обещание может быть доступно из разных потоков, в зависимости от использования.
Также, Ответ Родригеса Дэвида тоже отлично:
Конец канала связи потребителя будет использовать стандарт std :: future
потреблять данные из общего состояния, в то время как поток производителя
будет использовать std :: обещание для записи в общее состояние.
Но в качестве альтернативы, почему бы просто не использовать std::mutex
на stl контейнере результатов, и один поток или пул потоков, чтобы действовать на контейнер? Что делает использование std::promise
вместо этого, купите меня, кроме некоторой дополнительной читабельности против контейнера результатов stl?
Контроль, кажется, лучше в std::promise
версия:
Следующий google-тест проходит как helgrind, так и drd, подтверждая, что для одного производителя и с использованием wait () мьютекс не нужен.
ТЕСТОВОЕ ЗАДАНИЕ
static unsigned MapFunc( std::string const& str )
{
if ( str=="one" ) return 1u;
if ( str=="two" ) return 2u;
return 0u;
}
TEST( Test_future, Try_promise )
{
typedef std::map<std::string,std::promise<unsigned>> MAP;
MAP my_map;
std::future<unsigned> f1 = my_map["one"].get_future();
std::future<unsigned> f2 = my_map["two"].get_future();
std::thread{
[ ]( MAP& m )
{
m["one"].set_value( MapFunc( "one" ));
m["two"].set_value( MapFunc( "two" ));
},
std::ref( my_map )
}.detach();
f1.wait();
f2.wait();
EXPECT_EQ( 1u, f1.get() );
EXPECT_EQ( 2u, f2.get() );
}
Вы не решили использовать promise
вместо из других вы используете promise
в выполнять future
в сочетании с остальными. Пример кода на cppreference.com дает пример использования всех четырех:
#include <iostream>
#include <future>
#include <thread>
int main()
{
// future from a packaged_task
std::packaged_task<int()> task([](){ return 7; }); // wrap the function
std::future<int> f1 = task.get_future(); // get a future
std::thread(std::move(task)).detach(); // launch on a thread
// future from an async()
std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });
// future from a promise
std::promise<int> p;
std::future<int> f3 = p.get_future();
std::thread( [](std::promise<int>& p){ p.set_value(9); },
std::ref(p) ).detach();
std::cout << "Waiting...";
f1.wait();
f2.wait();
f3.wait();
std::cout << "Done!\nResults are: "<< f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
}
печать
Ожидание … Готово!
Результаты: 7 8 9
Фьючерсы используются со всеми тремя потоками, чтобы получить свои результаты, и promise
используется с третьим, чтобы выполнить future
другими способами, чем возвращаемое значение. Кроме того, один поток может выполнять несколько future
с различными значениями через promise
с, что он не может сделать иначе.
Простой способ думать о том, что вы можете установить future
возвращая значение или используя promise
, future
не имеет set
Способ; эта функциональность обеспечивается promise
, Вы выбираете то, что вам нужно, исходя из того, что позволяет ситуация.
Когда у вас есть два уровня асинхронности, вам нужно использовать обещание.
Например:
void fun()
{
std::promise<int> p;
std::future<int> f = p.get_future();
std::future<void> f2;
auto f3 = std::async([&]{
// Do some other computation
f2 = std::async([&]{ p.set_value(42);});
// Do some other work
});
// Do some other work
// Now wait for the result of async work;
std::cout << f.get();
// Do some other work again
// Wait for async threads to complete
f3.wait();
f2.wait();
}