Блокировка на многих шлюзах/фьючерсах и т.д. пока не будет готова
Можно ли блокировать группу блокировок/фьючерсов/любых блокируемых объектов, пока не будет готова какая-либо из них? Идея состоит в том, что мы можем сделать:
std::vector<std::future<T>> futures = ...;
auto ready_future = wait_until_an_element_is_ready(futures);
process(ready_future.get());
Я помню, что такие библиотеки, как libevent, libev и libuv, обладают такими способностями для задач ввода-вывода. Но я не знаю, можно ли это сделать для блокировок/фьючерсов.
Как я думал, чтобы добиться этого, нужно, чтобы фьючерсы вызывали обработчика после его завершения, но в то же время сравнивали и заменяли обработчик на нуль, чтобы другие фьючерсы не могли его назвать. Это требует координации фьючерсов, однако это не может быть сделано для блокировок.
ОБНОВЛЕНИЕ:. Похоже, что это предложение для него для С++ 2x.
Ответы
Ответ 1
Если у вас есть возможность повышения, ее возможности многопоточности намного превышают стандартную библиотеку.
Это будет иметь место после С++ 17, а также при взгляде на вещи, даже после С++ 20.
#include <future>
#include <vector>
#define BOOST_THREAD_VERSION 4
#include <boost/thread.hpp>
void wait_for_any(std::vector<boost::future<void>> & v)
{
boost::wait_for_any(std::begin(v), std::end(v));
}
Ответ 2
Альтернатива 1:
В настоящее время это невозможно, но вы можете обойти это, написав собственную логику поиска.
template<typename Iterator>
Iterator find_first_ready_future(Iterator begin, Iterator end)
{
return std::find_if(begin, end, [](auto &future) { return future.wait_for(0s) == std::future_status::ready; }
}
template<typename T>
std::future<T> wait_until_an_element_is_ready(std::vector<std::future<T>> &vector)
{
assert(!vector.empty());
auto iterator = vector.end();
do
{
// force switch of threads (if you don't want a busy loop, you might want to sleep this thread)
// If reaction speed is very important, you might want to skip this first yield/sleep in the first iteration.
std::this_thread::yield();
iterator = find_first_ready_future(vector.begin(), vector.end());
} while (iterator == vector.cend());
auto result = std::move(*iterator);
vector.erase(iterator); // Remove the ready future to prepare for the next call. (You ain't allowed to call .get() twice)
return result;
}
Обратите внимание, что все фьючерсы должны быть созданы с помощью флага async, потому что это станет бесконечным циклом, если они "отложены".
PS: Если вы не хотите, чтобы это блокировалось для основного потока, вы можете выполнить его в своем потоке/будущем.
Альтернатива 2:
Другой альтернативой было бы обернуть ваши фьючерсы для выполнения задачи. Это немного похоже на предложение future.then
:
template<typename T>
std::vector<std::future<void>> executeOnComplete(std::vector<std::future<T>> &&v)
{
std::vector<std::future<void>> result;
result.reserve(v.size());
for (auto &f : v)
result.emplace(std::async(std::launch::async,
[f = std::move(f)] { process(f.get()); }));
return result;
}
Эта альтернатива создает новый поток для каждого будущего и блокирует его до тех пор, пока исходное будущее не будет готово. Это связано с тем, что вы создаете слишком много потоков.
PS: С помощью какой-то фантастической логики шаблона result_of
вы можете даже создавать фьючерсы, которые возвращают результат `process