c++11 多線程入門教程(一)

 

原文做者:aircrafthtml

原文連接:https://www.cnblogs.com/DOMLX/p/10945309.html前端

    

 

      最近在找c++服務端開發的實習(大佬們有推薦嗎QAQ。。),剛好寫了一些c++11多線程有關的東西,就寫一下筆記留着之後本身忘記回來看吧,也不是專門寫給讀者看的,我就想到哪就寫到哪吧python

 

  c++11呢,就是c++升級以後的一個版本,如今立刻就出c++20了,裏面增長了不少對多線程支持的類,讓多線程編程更加簡單了,好了廢話很少說,先來創建一個簡單的多線程編程案例,看看c++11下多線程編程建立到底有多麼的簡單。ios

 

1.建立一個簡單的多線程案例:c++

首先導入#include<thread>---用於建立線程
程序員

其次導入#include<chrono>--用於時間延時 獲取時間之類的編程

定義一個線程對象t1,這就自動建立了一個線程,參數就是你要線程去執行的函數,t1是變量名字 隨便取
windows

std::thread t1(func);後端

下面這裏返回一個毫秒級別的時間間隔參數值,間隔10毫秒  promise

std::chrono::milliseconds(10)

this_thread::sleep_for()就是讓此線程休眠,能夠傳入休眠的時間

this_thread::sleep_for(std::chrono::milliseconds(10));讓本線程休眠10毫秒

 

好了知道這些參數意思就好了,看一下代碼:

#include<windows.h> #include <iostream> #include <chrono> #include <thread> using namespace std; int number = 1; int ThreadProc1() { while (number < 100) { cout << "thread 1 :" << number << endl; ++number; this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int ThreadProc2() { while (number < 100) { cout << "thread 2 :" << number << endl; ++number; this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.join(); t2.join(); system("pause"); return 0; }

  join()就是阻塞線程,直到線程函數執行完畢,若是函數有返回值,在這裏會直接忽略。阻塞的目的就是讓Main主線程等待一下建立的線程,省得我函數還在跑,程序就直接結束了。

  若是不想阻塞在這裏就將join()換成使用線程的detach()方法,將線程與線程對象分離,線程就能夠繼續運行下去,而且不會形成影響。

  從示例能夠看到c++11下建立多線程多麼方便了吧 ,比在Linux下用posix建立還簡便,而這個也是能夠在windows使用的(想一想windows下多線程的代碼,看着都頭疼好吧,亂七八糟一大堆)。

 

2.互斥量的使用

  跟往常的多線程同樣,多線程在運行過程當中都會對臨界區進行訪問,也就是一塊兒訪問共享資源。這樣就會形成一個問題,當兩個線程都要對一個變量int value值假如爲11,加一時,線程一取出11 進行加一尚未存入value,這時候線程二又取得value的11進行加一,而後線程一存入12,線程二又存入12,這就導入兩個線程訪問衝突,也就是臨界區問題。因此引進互斥量來解決。

導入#include <mutex>

代碼案例:

一個線程對變量number進行加一100次,另一個減一100次,最後結果應該仍是原來的值0。

 

#include<windows.h>
#include <iostream> #include <chrono> #include <thread> #include <mutex> using namespace std; int number = 0; mutex g_lock; int ThreadProc1() { for (int i = 0; i < 100; i++) { g_lock.lock(); ++number; cout << "thread 1 :" << number << endl; g_lock.unlock(); this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int ThreadProc2() { for (int i = 0; i < 100; i++) { g_lock.lock(); --number; cout << "thread 2 :" << number << endl; g_lock.unlock(); this_thread::sleep_for(std::chrono::milliseconds(10)); } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }

 

 

上面的每次都要對mutex變量進行鎖以及解鎖,有時候忘記解鎖就涼涼了。因此c++11還提供了一個lock_guard類,它利用了RAII機制能夠保證安全釋放mutex。

在std::lock_guard對象構造時,傳入的mutex對象(即它所管理的mutex對象)會被當前線程鎖住。在lock_guard對象被析構時,它所管理的mutex對象會自動解鎖,不須要程序員手動調用lock和unlock對mutex進行上鎖和解鎖操做。lock_guard對象並不負責管理mutex對象的生命週期,lock_guard對象只是簡化了mutex對象的上鎖和解鎖操做,方便線程對互斥量上鎖,即在某個lock_guard對象的生命週期內,它所管理的鎖對象會一直保持上鎖狀態;而lock_guard的生命週期結束以後,它所管理的鎖對象會被解鎖。程序員能夠很是方便地使用lock_guard,而不用擔憂異常安全問題。

代碼:

 

#include<windows.h> #include <iostream> #include <chrono> #include <thread> #include <mutex> using namespace std; int number = 0; mutex g_lock; int ThreadProc1() { lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { ++number; cout << "thread 1 :" << number << endl; } //this_thread::sleep_for(std::chrono::milliseconds(100)); return 0; } int ThreadProc2() { lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { --number; cout << "thread 2 :" << number << endl; //this_thread::sleep_for(std::chrono::milliseconds(10));  } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }

 

除了lock_guard,以外c++11還提供了std::unique_lock

類 unique_lock 是通用互斥包裝器,容許延遲鎖定、鎖定的有時限嘗試、遞歸鎖定、全部權轉移和與條件變量一同使用
unique_lock比lock_guard使用更加靈活,功能更增強大。
使用unique_lock須要付出更多的時間、性能成本。

#include <iostream>       // std::cout #include <thread> // std::thread #include <mutex> // std::mutex, std::unique_lock #include <vector> std::mutex mtx; // mutex for critical section std::once_flag flag; //定義一個once_flag類型的變量做爲call_once參數, //用std::call_once來保證多線程環境中只被調用一次 void print_block (int n, char c) { //unique_lock有多組構造函數, 這裏std::defer_lock不設置鎖狀態 std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock); //嘗試加鎖, 若是加鎖成功則執行 //(適合定時執行一個job的場景, 一個線程執行就能夠, 能夠用更新時間戳輔助) if(my_lock.try_lock()){ for (int i=0; i<n; ++i) std::cout << c; std::cout << '\n'; } } void run_one(int &n){ std::call_once(flag, [&n]{n=n+1;}); //只執行一次, 適合延遲加載; 多線程static變量狀況 } int main () { std::vector<std::thread> ver; int num = 0; for (auto i = 0; i < 10; ++i){ ver.emplace_back(print_block,50,'*'); ver.emplace_back(run_one, std::ref(num)); //emplace_back比push_back更好 是c++11增長的  } for (auto &t : ver){ t.join(); } std::cout << num << std::endl; return 0; } 

  

 這裏還要補充一下跟互斥量很像的條件變量的知識。

條件變量std::condition_variable的使用

  std::condition_variable 是爲了解決死鎖而生的。當互斥操做不夠用而引入的。好比,線程可能須要等待某個條件爲真才能繼續執行,而一個忙等待循環中可能會致使全部其餘線程都沒法進入臨界區使得條件爲真時,就會發生死鎖。因此,condition_variable實例被建立出現主要就是用於喚醒等待線程從而避免死鎖。std::condition_variable的 notify_one()用於喚醒一個線程;notify_all() 則是通知全部線程。
C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal同樣,可讓線程休眠,直到別喚醒,如今在重新執行。線程等待在多線程編程中使用很是頻繁,常常須要等待一些異步執行的條件的返回結果。
示例代碼:

#include<iostream>
#include<thread> #include<condition_variable> #include<mutex> #include<chrono> std::mutex g_mu; std::condition_variable g_vc; bool g_ready = false; void dispaly_id(int id) { std::unique_lock<std::mutex> lck(g_mu); g_vc.wait(lck, []() {return g_ready; }); //線程阻塞,直到第二個參數返回值爲真  std::cout << "id:" << id << std::endl; } void ready() { std::unique_lock<std::mutex> lck(g_mu); g_ready = true; g_vc.notify_all(); //喚醒全部的等待線程 } int main() { std::thread t[8]; for (int i = 0; i < 8; i++) { t[i] = std::thread(dispaly_id, i); } std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "all thread lock......" << std::endl; ready(); for (auto & th : t) th.join(); system("pause"); return 0; }

 

 

3.原子變量的使用

  在新標準C++11,引入了原子操做的概念,原子操做更接近內核,並經過這個新的頭文件提供了多種原子操做數據類型,例如,atomic_bool,atomic_int等等,若是咱們在多個線程中對這些類型的共享資源進行操做,編譯器將保證這些操做都是原子性的,也就是說,確保任意時刻只有一個線程對這個資源進行訪問,編譯器將保證,多個線程訪問這個共享資源的正確性。從而避免了鎖的使用,提升了效率。

  上面咱們用互斥鎖來實現加一百次,減小一百次。使用原子變量會更加簡潔。

 

#include<windows.h> #include <iostream> #include <chrono> #include <thread> #include <mutex> #include <atomic> using namespace std; atomic<int> number(0);//定義原子變量 一次只容許一個線程對其進行訪問 //int number = 0; //mutex g_lock; int ThreadProc1() { //lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { ++number; cout << "thread 1 :" << number << endl; } //this_thread::sleep_for(std::chrono::milliseconds(100)); return 0; } int ThreadProc2() { //lock_guard<mutex> loker(mutex); for (int i = 0; i < 100; i++) { --number; cout << "thread 2 :" << number << endl; //this_thread::sleep_for(std::chrono::milliseconds(10));  } return 0; } int main() { thread t1(ThreadProc1); thread t2(ThreadProc2); t1.detach(); t2.detach(); system("pause"); return 0; }

 

能夠看到使用了原子變量以後,代碼簡化了不少,以及之後對某些共享資源咱們均可以酌情的定義爲原子變量類型,很方便有木有。。。。。

 

 4.future與promise的使用

  在c++11中增長的線程庫很方便的讓咱們去使用線程,可是由於作出了一些改變,咱們並不能像往常同樣直接使用thread.join()獲取線程函數的返回值了,而咱們有時候又確實要利用線程函數的返回值。

  而thread庫提供了future用來訪問異步操做的結果,由於一個異步操做的結果每每不能當即獲取,只能在將來的某個時候從某個地方獲取,這個異步操做的結果是一個將來的期待值,因此被稱爲future

  future和promise的做用是在不一樣線程之間傳遞數據。

假設線程1須要線程2的數據,那麼組合使用方式以下:

  1.     線程1初始化一個promise對象和一個future對象,promise傳遞給線程2,至關於線程2對線程1的一個承諾;future至關於一個接受一個承諾,用來獲取將來線程2傳遞的值
  2.     線程2獲取到promise後,須要對這個promise傳遞有關的數據,以後線程1的future就能夠獲取數據了。
  3.     若是線程1想要獲取數據,而線程2未給出數據,則線程1阻塞,直到線程2的數據到達

示例代碼:

#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> void disPlay(std::future<int>& value) { std::cout << "wait some times......" << std::endl; auto result = value.get(); //沒有獲取到值會阻塞等待獲取 std::cout << "Value:" << result << std::endl; } int main() { std::promise<int> promise; std::future<int> value = promise.get_future(); //將promise與future綁定  std::thread t1(disPlay, std::ref(value)); //建立線程而且函數傳參,ref()是傳一個引用 std::this_thread::sleep_for(std::chrono::seconds(1)); //線程延時1秒 //給線程傳值進去 promise.set_value(15); t1.join(); system("pause"); return 0; }

  獲取future的結果有三種方式上面是get()獲取異步結果值返回,還有wait()等待異步操做完成,以及wait_for()超時等待返回結果。

 

 5.future與package_task的使用

  std::packaged_task包裝一個可調用的對象,而且容許異步獲取該可調用對象產生的結果。
std::packaged_task將其包裝的可調用對象的執行結果傳遞給一個std::future對象,與std::promise某種程度上是很像的,promise保存一個共享狀態的值,而package_task保存的是一個函數。

 

示例代碼:

#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> inline int func(int x) { return x + 6; } int main() { std::packaged_task<int(int)> tsk(func); std::future<int> fut = tsk.get_future(); //獲取future綁定起來  std::thread(std::move(tsk), 2).detach();//直接將task轉移做爲線程函數使用  auto value = fut.get(); std::cout << "result:" << value << std::endl; system("pause"); return 0; }

 

 6.線程異步操做函數async的用法

  ,std::async比std::packaged_task,std::promise中,std::thread更高一層,它能夠直接用來建立異步的task,異步的結果也保存在future中。完成後,外面再經過future.get/wait來獲取這個將來的結果,強烈推薦使用async,咱們不須要關注異步任務的結果,只要等待任務完成獲取值就好了。

  如今來看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一個參數是線程的建立策略,有兩種策略,默認的策略是當即建立線程:

 

  • std::launch::async:在調用async就開始建立線程。
  • std::launch::deferred:延遲加載方式建立線程。調用async時不建立線程,直到調用了future的get或者wait時才建立線程。

 

第二個參數是線程函數,第三個參數是線程函數的參數。

代碼示例:

#include <iostream>
#include <chrono> #include <thread> #include <mutex> #include <atomic> #include <future> #include <vector> int main() { std::future<int> fut = std::async(std::launch::async, []() { return 9; }); std::cout << "result:" << fut.get() << std::endl; system("pause"); return 0; }

  []()這是c++11裏面lambda表達式用法

 

7.std::future::wait_for()函數做用

  函數原型:

template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep,Period>& timeout_duration ) const;
 

  等待結果變得可用。阻塞直至通過指定的 timeout_duration ,或結果變爲可用,二者的先到來者。返回值鑑別結果的狀態。

此函數可能因爲調度或資源爭議延遲而阻塞長於 timeout_duration

推薦標準庫用穩定時鐘度量時長。若實現用系統時鐘代替,則等待時間可能也對時鐘調整敏感。

若調用此函數前 valid()== false 則行爲未定義。

參數

timeout_duration - 要阻塞的最大時長

返回值

 
常量 解釋
future_status::deferred 要計算結果的函數仍未啓動
future_status::ready 結果就緒
future_status::timeout 已通過時限

異常

時鐘、時間點或時長在執行中可能拋的任何異常(標準庫提供的時鐘、時間點和時長決不拋出)。

注意

鼓勵實如今調用前檢測 valid == false 的狀況並拋出以 future_errc::no_state 爲 error_condition 的 future_error

 

代碼示例:

#include <iostream>
#include <future> #include <thread> #include <chrono> int main() { std::future<int> future = std::async(std::launch::async, [](){ std::this_thread::sleep_for(std::chrono::seconds(3)); return 8; }); std::cout << "waiting...\n"; std::future_status status; do { status = future.wait_for(std::chrono::seconds(1)); if (status == std::future_status::deferred) { std::cout << "deferred\n"; } else if (status == std::future_status::timeout) { std::cout << "timeout\n"; } else if (status == std::future_status::ready) { std::cout << "ready!\n"; } } while (status != std::future_status::ready); std::cout << "result is " << future.get() << '\n'; }

可能結果:

waiting...
timeout
timeout
ready! result is 8

 

後面還會出不少一系列的入門教程,能夠關注我噢。(我博客難道寫的不清楚嗎,大家還不關注我???小聲bb)。。。。hhhhhhhh

 

也能夠補一下基礎多線程編程教程以下:

c++ 網絡編程課設入門超詳細教程 ---目錄

 

如有興趣交流分享技術,可關注本人公衆號,裏面會不按期的分享各類編程教程,和共享源碼,諸如研究分享關於c/c++,python,前端,後端,opencv,halcon,opengl,機器學習深度學習之類有關於基礎編程,圖像處理和機器視覺開發的知識

相關文章
相關標籤/搜索