簡介
很多時候,我們的可調用對象需要某些非參數依賴的執行環境。可以這麼理解,我們的任務是就緒的,不需要依賴外界傳入參數了,只需要以某種方式控制方法的when
、where
和how
即可。
三個分別是指:
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
延後執行等。下面以代碼示例,簡單演示下支持Executor
的async_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