2017-06-04 63 views
2

考慮http://en.cppreference.com/w/cpp/experimental/when_any。以下僅僅是一個天真簡化實現:如何在不進行輪詢的情況下實現std :: 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); 
      } 
     } 
    } 
} 

我很不滿意,因爲它是一個無限循環繁忙輪詢。

有沒有辦法避免忙碌的投票?

+1

這不是'when_any()'的作用。 – Barry

+2

這是['wait_for_any'](http://www.boost.org/doc/libs/1_64_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.wait_for_any) 'when_any'](http://www.boost.org/doc/libs/1_64_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.when_any)。 –

+0

這只是一個天真而簡化的版本。關鍵問題是如何避免輪詢。 – xmllmx

回答

5

無輪詢版本將在未來啓動1個線程,並讓它們設置一個條件變量,以便將來做好準備。

然後你「泄漏」線程,直到期貨準備就緒,同時返回準備好的事實。

這很糟糕。但沒有投票。

要做得更好,您需要有一個可以設置(並理想地移除)的延續的未來。然後,你只需要讓期貨在完成後通知你,然後等待。這需要修改或編寫自己的未來。

這是繼續和when_any都提出標準化的原因之一。你將來需要他們。

現在,如果你有自己的系統,你可以根據一個線程安全的隊列來傳遞東西而不是期貨,通過條件變量實現。這需要在「未來」創造點進行合作。

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都準備好。

+0

當線程被阻塞時,你將如何恢復除了準備好的期貨以外的期貨? –

+0

@yurikilochek這些線程會產生未來,當他們正在等待的未來準備就緒時,就會做好準備。實際上,你正在'future '中包裝'未來',它*在* *自己準備好之前觸發一個(共享)條件變量。 – Yakk

+0

@Okay,這可以工作,但是處理結果的一個典型用例,從集合中移除ready ready,並在循環中等待其餘的,會產生瘋狂的線程數量。 –

3

不是真的,沒有延續的期貨的實用性非常有限。

如果您不得不這樣做,並使用std::future,我建議通過.wait_for()更智能投票與increasing timeouts

0

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共享狀態。共享狀態保存一對一(m_done)和一對二(m_count_to_two)。每執行一次延續將增加數一對一;如果它是贏家,它也會增加數到二。主線程在完成所有這些設置後也會增加count-to-two。一旦二進制數達到2(表示主線程完成設置至少已經執行了一次延期),我們將根據對應於when_any未來的承諾呼叫set_value。噹噹!

相關問題