理解C++ Executor的設計理念

簡介

很多時候,我們的可調用對象需要某些非參數依賴的執行環境。可以這麼理解,我們的任務是就緒的,不需要依賴外界傳入參數了,只需要以某種方式控制方法的whenwherehow即可。
三個分別是指:

  • where:可調用對象的執行位置,比如可以在內部或者外部的處理器(線程)上執行,並從內部或者外部的處理器(線程)上獲取結果
  • when:可調用對象的執行時間,比如立刻執行或者被調度執行
  • how:可調用對象的執行方式,比如在CPU或者GPU上執行,可以矢量化計算等

舉個例子:

int foo(int i) {
	return i * 2;
}

// ...
auto f = std::async(std::launch::async, foo(10));
f.get();
// ...

上述代碼中,對於foo(10)來說,關鍵要素是:

  • when:調用std::async的時候
  • where:在std::async內部啓動的新線程中
  • how:在CPU上執行,正常順序執行

很多時候,用戶想要獲取一個通用的接口類,這個類提供了統一的接口,而且規定了上述的3w,之後只需要把可執行對象傳入接口類中,就能根據該接口類的3w,最終獲取結果。

那麼,我們可以把上面說的接口類,認爲是一個Executor。甚至實際的Executor甚至僅僅是個接口,僅提供了統一的調用方法,讓用戶自己去實現有關實際功能等。

因此,可以這麼理解,Executor給可調用的對象提供了一個運行環境或者說是運行上下文環境 Execute Context。個人理解,運行上下文是區別於平時說的上下文 Context。因爲Context是針對編程環境來說的,或者認爲是Context決定了可調用對象的運行形式,能改變可調用對象的表現;而Execute Context無法改變上面的,其只能改變我們說的3w,而3w不決定可調用對象的結果。

比如Executor可以是不同類型的線程池,比如一個線程一個隊列或者多線程共享隊列等。只要指定了Executor,用戶就可以傳入可調用對象,並等待結果;它屏蔽了底層的執行細節。

C++目前還不支持Executor的模式,或者說支持的比較弱。。比如std::async中,無法指定Executor,只能以std::launch::async啓動新的線程或者std::launch::defered延後執行等。下面以代碼示例,簡單演示下支持Executorasync_exec函數。

代碼示例

以一個多線程共享隊列的線程池:

#include <thread>
#include <iostream>
#include <functional>
#include <vector>
#include <mutex>
#include <condition_variable>
#include <deque>
#include <random>
#include <atomic>

// Executor,僅僅提供接口
class Executor {
public:
    Executor() = default;

    virtual ~Executor() = default;

    virtual void add(std::function<void()> fn) = 0;
};

// 實際的線程池,Executor的具體表現,可以忽略這部分的具體實現
class ThreadPool : public Executor {
public:
    explicit ThreadPool(size_t threadCnt = 4) : m_threadCnt(4), m_stop(false) {
        std::thread([this]() { run(); }).detach(); // 後臺啓動
    }

    ~ThreadPool() override {
        m_stop = true;
        m_cond.notify_all();
        for (auto &t: m_threads) {
            if (t.joinable()) {
                t.join();
            }
        }
    }

    ThreadPool(ThreadPool &) = delete;

    ThreadPool(ThreadPool &&) = delete;

    void add(std::function<void()> fn) override {
        m_mtx.lock();
        m_tasks.emplace_back(std::move(fn));
        m_mtx.unlock();
        m_cond.notify_one();
    }

private:
    void run() {
        for (size_t i = 0; i < m_threadCnt; ++i) {
            m_threads.emplace_back(std::thread([this]() {
                for (;;) {
                    std::unique_lock<std::mutex> lck(m_mtx);
                    m_cond.wait(lck, [this]() -> bool { return m_stop || !m_tasks.empty(); });
                    if (m_stop) {
                        return;
                    }
                    auto &fn = m_tasks.front();
                    m_tasks.pop_front();
                    lck.unlock(); // 一定要釋放掉鎖,否則無法併發

                    fn();
                }
            }));
        }
    }

private:
    std::vector<std::thread> m_threads;
    std::deque<std::function<void()>> m_tasks;
    std::condition_variable m_cond;
    std::mutex m_mtx;
    std::atomic<bool> m_stop{false};
    size_t m_threadCnt;
};

// 這裏泛型提供了使用Exec的例子
template<typename Fn, typename Exec = Executor>
void async_exec(Fn fn, Exec &exec) {
    exec.add(fn);
}

// 工具函數,獲取隨機數
inline int get_random(int low, int high) {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> distrib(low, high);
    return distrib(gen);
}

void foo(int n) {
    int t = get_random(100, 5000);
    std::this_thread::sleep_for(std::chrono::milliseconds(t)); // 模擬執行任務
    std::cout << "foo finish task " << n << " with " << t << " ms\n";
}

int main() {
    ThreadPool tp(4);
    for (int i = 0; i < 10; ++i) {
        async_exec([i]() { foo(i); }, tp); // 指定實際的Executor執行
    }
    std::this_thread::sleep_for(std::chrono::seconds(15));  // 休眠15s等所有任務完成
    std::cout << "stop everything\n";
    return 0;
}

參考

  • https://github.com/facebook/folly/blob/master/folly/docs/Executors.md
  • https://www.modernescpp.com/index.php/a-short-detour-executors
  • http://www.vollmann.ch/en/presentations/executors2018.pdf
  • https://stackoverflow.com/questions/42177803/what-is-the-executor-pattern-in-a-c-context
  • https://www.linuxtopia.org/online_books/programming_books/c++_practical_programming/c++_practical_programming_267.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章