c++11關於併發引入了好多好東西,這裏按照以下順序介紹:html
c++11以前你可能使用pthread_xxx來建立線程,繁瑣且不易讀,c++11引入了std::thread來建立線程,支持對線程join或者detach。直接看代碼:ios
#include <iostream> #include <thread> using namespace std; int main() { auto func = []() { for (int i = 0; i < 10; ++i) { cout << i << " "; } cout << endl; }; std::thread t(func); if (t.joinable()) { t.detach(); } auto func1 = [](int k) { for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread tt(func1, 20); if (tt.joinable()) { // 檢查線程能否被join tt.join(); } return 0; }
上述代碼中,函數func和func1運行在線程對象t和tt中,從剛建立對象開始就會新建一個線程用於執行函數,調用join函數將會阻塞主線程,直到線程函數執行結束,線程函數的返回值將會被忽略。若是不但願線程被阻塞執行,能夠調用線程對象的detach函數,表示將線程和線程對象分離。c++
若是沒有調用join或者detach函數,假如線程函數執行時間較長,此時線程對象的生命週期結束調用析構函數清理資源,這時可能會發生錯誤,這裏有兩種解決辦法,一個是調用join(),保證線程函數的生命週期和線程對象的生命週期相同,另外一個是調用detach(),將線程和線程對象分離,這裏須要注意,若是線程已經和對象分離,那咱們就再也沒法控制線程何時結束了,不能再經過join來等待線程執行完。編程
這裏能夠對thread進行封裝,避免沒有調用join或者detach可致使程序出錯的狀況出現:promise
class ThreadGuard { public: enum class DesAction { join, detach }; ThreadGuard(std::thread&& t, DesAction a) : t_(std::move(t)), action_(a){}; ~ThreadGuard() { if (t_.joinable()) { if (action_ == DesAction::join) { t_.join(); } else { t_.detach(); } } } ThreadGuard(ThreadGuard&&) = default; ThreadGuard& operator=(ThreadGuard&&) = default; std::thread& get() { return t_; } private: std::thread t_; DesAction action_; }; int main() { ThreadGuard t(std::thread([]() { for (int i = 0; i < 10; ++i) { std::cout << "thread guard " << i << " "; } std::cout << std::endl;}), ThreadGuard::DesAction::join); return 0; }
c++11還提供了獲取線程id,或者系統cpu個數,獲取thread native_handle,使得線程休眠等功能安全
std::thread t(func); cout << "當前線程ID " << t.get_id() << endl; cout << "當前cpu個數 " << std::thread::hardware_concurrency() << endl; auto handle = t.native_handle();// handle可用於pthread相關操做 std::this_thread::sleep_for(std::chrono::seconds(1));
std::mutex是一種線程同步的手段,用於保存多線程同時操做的共享數據。多線程
mutex分爲四種:併發
拿一個std::mutex和std::timed_mutex舉例吧,別的都是相似的使用方式:異步
std::mutex:async
#include <iostream> #include <mutex> #include <thread> using namespace std; std::mutex mutex_; int main() { auto func1 = [](int k) { mutex_.lock(); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; mutex_.unlock(); }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0; }
std::timed_mutex:
#include <iostream> #include <mutex> #include <thread> #include <chrono> using namespace std; std::timed_mutex timed_mutex_; int main() { auto func1 = [](int k) { timed_mutex_.try_lock_for(std::chrono::milliseconds(200)); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; timed_mutex_.unlock(); }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0; }
這裏主要介紹兩種RAII方式的鎖封裝,能夠動態的釋放鎖資源,防止線程因爲編碼失誤致使一直持有鎖。
c++11主要有std::lock_guard和std::unique_lock兩種方式,使用方式都相似,以下:
#include <iostream> #include <mutex> #include <thread> #include <chrono> using namespace std; std::mutex mutex_; int main() { auto func1 = [](int k) { // std::lock_guard<std::mutex> lock(mutex_); std::unique_lock<std::mutex> lock(mutex_); for (int i = 0; i < k; ++i) { cout << i << " "; } cout << endl; }; std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(func1, 200); } for (auto& th : threads) { th.join(); } return 0; }
std::lock_gurad相比於std::unique_lock更加輕量級,少了一些成員函數,std::unique_lock類有unlock函數,能夠手動釋放鎖,因此條件變量都配合std::unique_lock使用,而不是std::lock_guard,由於條件變量在wait時須要有手動釋放鎖的能力,具體關於條件變量後面會講到。
c++11提供了原子類型std::atomic<T>,理論上這個T能夠是任意類型,可是我平時只存放整形,別的還真的沒用過,整形有這種原子變量已經足夠方便,就不須要使用std::mutex來保護該變量啦。看一個計數器的代碼:
struct OriginCounter { // 普通的計數器 int count; std::mutex mutex_; void add() { std::lock_guard<std::mutex> lock(mutex_); ++count; } void sub() { std::lock_guard<std::mutex> lock(mutex_); --count; } int get() { std::lock_guard<std::mutex> lock(mutex_); return count; } }; struct NewCounter { // 使用原子變量的計數器 std::atomic<int> count; void add() { ++count; // count.store(++count);這種方式也能夠 } void sub() { --count; // count.store(--count); } int get() { return count.load(); } };
是否是使用原子變量更加方便了呢?
c++11提供了std::call_once來保證某一函數在多線程環境中只調用一次,它須要配合std::once_flag使用,直接看使用代碼吧:
std::once_flag onceflag; void CallOnce() { std::call_once(onceflag, []() { cout << "call once" << endl; }); } int main() { std::thread threads[5]; for (int i = 0; i < 5; ++i) { threads[i] = std::thread(CallOnce); } for (auto& th : threads) { th.join(); } return 0; }
貌似把volatile放在併發裏介紹不太合適,可是貌似不少人都會把volatile和多線程聯繫在一塊兒,那就一塊兒介紹下吧。
volatile一般用來創建內存屏障,volatile修飾的變量,編譯器對訪問該變量的代碼一般再也不進行優化,看下面代碼:
int *p = xxx; int a = *p; int b = *p;
a和b都等於p指向的值,通常編譯器會對此作優化,把*p的值放入寄存器,就是傳說中的工做內存(不是主內存),以後a和b都等於寄存器的值,可是若是中間p地址的值改變,內存上的值改變啦,但a,b仍是從寄存器中取的值(不必定,看編譯器優化結果),這就不符合需求,因此在此對p加volatile修飾能夠避免進行此類優化。
注意:volatile不能解決多線程安全問題,針對特種內存才須要使用volatile,它和atomic的特色以下:
條件變量是c++11引入的一種同步機制,它能夠阻塞一個線程或者個線程,直到有線程通知或者超時纔會喚醒正在阻塞的線程,條件變量須要和鎖配合使用,這裏的鎖就是上面介紹的std::unique_lock。
這裏使用條件變量實現一個CountDownLatch:
class CountDownLatch { public: explicit CountDownLatch(uint32_t count) : count_(count); void CountDown() { std::unique_lock<std::mutex> lock(mutex_); --count_; if (count_ == 0) { cv_.notify_all(); } } void Await(uint32_t time_ms = 0) { std::unique_lock<std::mutex> lock(mutex_); while (count_ > 0) { if (time_ms > 0) { cv_.wait_for(lock, std::chrono::milliseconds(time_ms)); } else { cv_.wait(lock); } } } uint32_t GetCount() const { std::unique_lock<std::mutex> lock(mutex_); return count_; } private: std::condition_variable cv_; mutable std::mutex mutex_; uint32_t count_ = 0; };
關於條件變量其實還涉及到通知丟失和虛假喚醒問題,由於不是本文的主題,這裏暫不介紹,你們有須要能夠留言。
c++11關於異步操做提供了future相關的類,主要有std::future、std::promise和std::packaged_task,std::future比std::thread高級些,std::future做爲異步結果的傳輸通道,經過get()能夠很方便的獲取線程函數的返回值,std::promise用來包裝一個值,將數據和future綁定起來,而std::packaged_task則用來包裝一個調用對象,將函數和future綁定起來,方便異步調用。而std::future是不能夠複製的,若是須要複製放到容器中可使用std::shared_future。
std::promise與std::future配合使用
#include <functional> #include <future> #include <iostream> #include <thread> using namespace std; void func(std::future<int>& fut) { int x = fut.get(); cout << "value: " << x << endl; } int main() { std::promise<int> prom; std::future<int> fut = prom.get_future(); std::thread t(func, std::ref(fut)); prom.set_value(144); t.join(); return 0; }
std::packaged_task與std::future配合使用
#include <functional> #include <future> #include <iostream> #include <thread> using namespace std; int func(int in) { return in + 1; } int main() { std::packaged_task<int(int)> task(func); std::future<int> fut = task.get_future(); std::thread(std::move(task), 5).detach(); cout << "result " << fut.get() << endl; return 0; }
更多關於future的使用能夠看我以前寫的關於線程池和定時器的文章。
三者之間的關係
std::future用於訪問異步操做的結果,而std::promise和std::packaged_task在future高一層,它們內部都有一個future,promise包裝的是一個值,packaged_task包裝的是一個函數,當須要獲取線程中的某個值,可使用std::promise,當須要獲取線程函數返回值,可使用std::packaged_task。
async是比future,packaged_task,promise更高級的東西,它是基於任務的異步操做,經過async能夠直接建立異步的任務,返回的結果會保存在future中,不須要像packaged_task和promise那麼麻煩,關於線程操做應該優先使用async,看一段使用代碼:
#include <functional> #include <future> #include <iostream> #include <thread> using namespace std; int func(int in) { return in + 1; } int main() { auto res = std::async(func, 5); // res.wait(); cout << res.get() << endl; // 阻塞直到函數返回 return 0; }
使用async異步執行函數是否是方便多啦。
async具體語法以下:
async(std::launch::async | std::launch::deferred, func, args...);
第一個參數是建立策略:
若是不明確指定建立策略,以上兩個都不是async的默認策略,而是未定義,它是一個基於任務的程序設計,內部有一個調度器(線程池),會根據實際狀況決定採用哪一種策略。
若從 std::async 得到的 std::future 未被移動或綁定到引用,則在完整表達式結尾, std::future的析構函數將阻塞直至異步計算完成,實際上至關於同步操做:
std::async(std::launch::async, []{ f(); }); // 臨時量的析構函數等待 f() std::async(std::launch::async, []{ g(); }); // f() 完成前不開始
注意
:關於async啓動策略這裏網上和各類書籍介紹的五花八門,這裏會以cppreference爲主。
有時候咱們若是想真正執行異步操做能夠對async進行封裝,強制使用std::launch::async策略來調用async。
template <typename F, typename... Args> inline auto ReallyAsync(F&& f, Args&&... params) { return std::async(std::launch::async, std::forward<F>(f), std::forward<Args>(params)...); }
關於c++11關於併發的新特性就介紹到這裏,你們有問題能夠給我留言~
https://blog.csdn.net/zhangzq...
https://zh.cppreference.com/w...
https://zhuanlan.zhihu.com/p/...
https://www.runoob.com/w3cnot...
https://zh.cppreference.com/w...
《深刻應用c++11:代碼優化與工程級應用》
《Effective Modern C++》更多文章,請關注個人V X 公 主 號:程序喵大人,歡迎交流。