如何在没有轮询的情况下实现std :: when_any?

时间:2017-06-04 15:34:48

标签: c++ multithreading concurrency standards c++17

考虑http://en.cppreference.com/w/cpp/experimental/when_any。以下是天真简化实施:

#include <future>

template<typename Iterator>
auto when_any(Iterator first, Iterator last)
{
    while (true)
    {
        for (auto pos = first; pos != last; ++pos)
        {
            if (pos->is_ready())
            {
                return std::move(*pos);
            }
        }
    }
}

我不满意,因为它是一个无限循环中的繁忙轮询。

有没有办法避免繁忙的轮询?

3 个答案:

答案 0 :(得分:5)

无轮询版本将在未来启动1个线程并让它们设置一个条件变量,以便将来做好准备。

然后你&#34;泄漏&#34;直到期货准备就绪的线程,同时返回一个准备就绪的事实。

这很糟糕。但没有民意调查。

为了做得更好,你需要拥有一个可以设置(并理想地删除)的延续的未来。然后你只要求期货在完成后通知你,然后等待。这需要修改或编写自己的未来。

这是继续和when_any都被提议用于标准化的原因之一。你将来需要它们。

现在,如果您拥有自己的系统,则可以将其基于线程安全队列,而不是期货,通过条件变量实现。这需要在&#34;未来&#34;创建

struct many_waiter_t {
  std::mutex m;
  std::condition_variable cv;
  std::vector<std::size_t> index;

  std::size_t wait() {
    auto l = lock();
    cv.wait(l, [this]{
      return !index.empty();
    });
    auto r = index.back();
    index.pop_back();
    return r;
  }
  void set( std::size_t N ) {
    {
      auto l = lock();
      index.push_back(N);
    }
    cv.notify_one();
  }
};
template<class T>
std::future<T> add_waiter( std::future<T> f, std::size_t i, std::shared_ptr<many_waiter_t> waiter )
{
  return std::async([f = std::move(f), waiter, i]{
    auto r = f.get();
    waiter.set(i);
    return r;
  });
}

使用期货fs的数组,我们可以生成一个新的期货f2s数组和一个服务员,这样服务员可以等待非自旋锁,直到未来准备就绪,并且f2s对应原始fs

您可以反复等待waiter,直到f2s都准备就绪。

答案 1 :(得分:3)

实际上,没有延续的期货的实用性非常有限。

如果您被迫执行此操作并使用std::future,我建议您使用.wait_for()通过increasing timeouts进行更智能的投票。

答案 2 :(得分:1)

I have posted an implementation of when_any over on CodeReview.正如Yakk在回答中所说,

  

为了做得更好,你需要拥有一个可以设置(并理想地删除)的延续的未来。然后你只要求期货在完成后通知你,然后wait。这需要修改或编写自己的未来。

所以我的实现依赖于future::then(),其主旨是:

template<class... Futures>
struct when_any_shared_state {
    promise<tuple<Futures...>> m_promise;
    tuple<Futures...> m_tuple;
    std::atomic<bool> m_done;
    std::atomic<bool> m_count_to_two;

    when_any_shared_state(promise<tuple<Futures...>> p) :
        m_promise(std::move(p)), m_done(false), m_count_to_two(false) {}
};

template<class... Futures>
auto when_any(Futures... futures) -> future<tuple<Futures...>>
{
    using shared_state = detail::when_any_shared_state<Futures...>;
    using R = tuple<Futures...>;
    promise<R> p;
    future<R> result = p.get_future();

    auto sptr = make_shared<shared_state>(std::move(p));
    auto satisfy_combined_promise =
        [sptr](auto f) {
            if (sptr->m_done.exchange(true) == false) {
                if (sptr->m_count_to_two.exchange(true)) {
                    sptr->m_promise.set_value(std::move(sptr->m_tuple));
                }
            }
            return f.get();
        };
    sptr->m_tuple = tuple<Futures...>(futures.then(satisfy_combined_promise)...);
    if (sptr->m_count_to_two.exchange(true)) {
        sptr->m_promise.set_value(std::move(sptr->m_tuple));
    }
    return result;
}

您为每个传入的future附加了一个续集(使用then)。此延续将shared_ptr保持为共享状态。共享状态包含count-to-one(m_done)和count-to-two(m_count_to_two)。执行的每个继续将增加count-to-one;如果它是赢家,它也会增加计数到两个。在完成所有这些操作后,主线程也会增加count-to-two。一旦计数到2已达到2(表示主线程已完成设置并且至少有一个延续已执行),我们将在对应的承诺上调用set_valuewhen_any回归未来。当当!