原文作者:aircraft
原文連結:https://www.cnblogs.com/DOMLX/p/10914162.html
最近是恰好寫了一些c++11多執行緒有關的東西,就寫一下筆記留著以後自己忘記回來看吧,也不是專門寫給讀者看的,我就想到哪就寫到哪吧
c++11呢,就是c++升級之後的一個版本,現在馬上就出c++20了,裡面增加了很多對多執行緒支援的類,讓多執行緒程式設計更加簡單了,好了廢話不多說,先來建立一個簡單的多執行緒程式設計案例,看看c++11下多執行緒程式設計建立到底有多麼的簡單。
1.建立一個簡單的多執行緒案例:
首先匯入#include<thread>---用於建立執行緒
其次匯入#include<chrono>--用於時間延時 獲取時間之類的
定義一個執行緒物件t1,這就自動建立了一個執行緒,引數就是你要執行緒去執行的函式,t1是變數名字 隨便取
std::thread t1(func);
下面這裡返回一個毫秒級別的時間間隔引數值,間隔10毫秒
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; }
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;
}
可以看到使用了原子變數之後,程式碼簡化了很多,以及以後對某些共享資源我們都可以酌情的定義為原子變數型別,很方便有木有。。。。。
本來想寫完幾個c++11多執行緒常用的函式和操作的,但是有點想去看一下哥斯拉2,但是哥斯拉1還沒看,先去看哥斯拉1好了 今天就先寫這麼多了hhhhhh