Windows 多執行緒程式設計入門(1)
看了網上一些說法,總結以下幾點:
1:從C++11開始,標準庫裡已經包含了對執行緒的支援,即:
std::thread
2:C++本身還支援pthread這個API來進行多執行緒程式設計。
3:自己常用Windows程式設計,還是擁抱一下C++11吧,簡單的學習一下使用方法。(雖然知乎上std::thread被黑成翔了~)
開始實踐
環境:WIN7、VS2015
構造新執行緒
1:std::thread的預設建構函式:
thread() noexcept
構造出的執行緒物件不會執行。
2:正常的初始化函式:
template<class T,class ...Args> explicit thread(T && t,Args&&... args);
函式功能:構造出一個新的執行緒,並直接開始執行,新執行的執行緒呼叫函式 t ,並傳遞 args 作為引數。
t 可以繫結函式物件,成員函式、移動建構函式
args 可以繫結一些函式的引數,若 t 是一個成員指標,那麼args的第一個引數就必須是一個物件,或是該物件的引用,或是該物件的指標。
3:刪除執行緒:
thread(const thread&) = delete;
一些函式的介紹
1:join() 函式
該函式會阻塞主執行緒,直到呼叫該函式的執行緒執行完畢才會繼續執行主執行緒
if (t1.joinable())
t1.join();
使用前最好加個判斷
2:detach函式
將子執行緒從主執行緒中分離,獨立執行,不會阻塞主執行緒
detach方式不會對當前程式碼造成影響,當前程式碼繼續執行,而建立的執行緒會同時併發執行
t1.detach
引數問題
建立的新執行緒對當前作用於的變數的使用:建立完之後,離開作用域,但是執行緒可能仍然在執行,那麼此時執行緒如果使用的是之前作用域中定義的區域性變數的引用或者指標,則會出現意想不到的錯誤。(值傳遞就不會出現這個問題)
轉移執行緒的所有權
thread是可移動的(movable)的,但不可複製(copyable)。可以通過move來改變執行緒的所有權,靈活的決定執行緒在什麼時候join或者detach。
thread t1(f1);
thread t3(move(t1));
將執行緒從t1轉移給t3,這時候t1就不再擁有執行緒的所有權,呼叫t1.join或t1.detach會出現異常,要使用t3來管理執行緒。這也就意味著thread可以作為函式的返回型別,或者作為引數傳遞給函式,能夠更為方便的管理執行緒。
獲取執行緒的ID
執行緒的標識型別為std::thread::id,有兩種方式獲得到執行緒的id。
* 通過thread的例項呼叫get_id()直接獲取
* 在當前執行緒上呼叫this_thread::get_id()獲取
t1.get_id();
互斥鎖
針對資料競爭的情況,可以使用執行緒互斥物件mutex保持資料同步。
mutex類的使用需要包含標頭檔案mutex
void thread01()
{
while (totalNum > 0)
{
mu.lock(); //同步資料鎖
cout << totalNum << endl;
totalNum--;
Sleep(100);
mu.unlock(); //解除鎖定
}
}
異常情況的處理
當決定以detach方式讓執行緒在後臺執行時,可以在建立thread的例項後立即呼叫detach,這樣執行緒就會後thread的例項分離,即使出現了異常thread的例項被銷燬,仍然能保證執行緒在後臺執行。
但執行緒以join方式執行時,需要在主執行緒的合適位置呼叫join方法,如果呼叫join前出現了異常,thread被銷燬,執行緒就會被異常所終結。
為了避免異常將執行緒終結,或者由於某些原因,例如執行緒訪問了區域性變數,就要保證執行緒一定要在函式退出前完成,就要保證要在函式退出前呼叫join。
一種比較好的方法是資源獲取即初始化(RAII,Resource Acquisition Is Initialization),該方法提供一個類,在解構函式中呼叫join。無論是何種情況,當函式退出時,區域性變數g呼叫其解構函式銷燬,從而能夠保證join一定會被呼叫。
class thread_guard
{
thread &t;
public:
explicit thread_guard(thread& _t) :
t(_t) {}
~thread_guard()
{
if (t.joinable())
t.join();
}
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};
void func() {
thread t([] {
cout << "Hello thread" << endl;
});
thread_guard g(t);
}
lambda表示式與thread
lambda本身就相當於一個函式(匿名函式),所以也可以當做引數來賦給執行緒物件
void TestLambda()
{
for (int i = 0; i < 100; i++)
{
std::thread t([i]() {
std::cout << i << std::endl;
});
t.detach();
}
}
好了,程式碼奉上:
/*time:20180605
author:MISAYAONE
*/
#include<iostream>
#include<thread>
#include<Windows.h>
#include<mutex>
using namespace std;
mutex mu;//互斥鎖物件
int num = 100;
void thread_func1()
{
for (int i=0;i<10;i++)
{
cout <<"thread 1's "<< i << endl;
Sleep(100);
}
while (num>0)
{
mu.lock();//互斥鎖加鎖
cout << num << endl;
num--;
mu.unlock();//互斥鎖解鎖
}
}
void thread_func2(int n)
{
for (int i = 0; i<n; i++)
{
cout <<"thread 2's "<< i << endl;
Sleep(200);
}
while (num>0)
{
mu.lock();
cout << num << endl;
num--;
mu.unlock();
}
}
class A
{
public :
void thread_func3()
{
for (int i = 0; i<10; i++)
{
cout << "class A thread 3's " << i << endl;
Sleep(200);
}
}
};
class thread_guard
{
thread &t;
public:
explicit thread_guard(thread& _t):t(_t){}
~thread_guard()
{
if (t.joinable())
{
t.join();
}
}
//拷貝賦值預設去除
thread_guard(const thread_guard&) = delete;
thread_guard& operator=(const thread_guard&) = delete;
};
int main()
{
A a;
thread task00();//預設建構函式
thread task01(thread_func1);//正常建構函式
thread_guard g(task01);//出現異常後,函式退出,g區域性物件對銷燬,此時g呼叫解構函式,會自動執行task01的join函式,從而能夠保證join一定會被呼叫
thread task02(thread_func2,10);//帶args的執行緒建構函式
thread task_i(move(task02));//轉移執行緒所有權,此時再利用task02呼叫join和detach報錯
thread task03(&A::thread_func3,a);//傳入類的成員函式作為執行緒建構函式的引數
//join函式順序執行執行緒
task01.join();
//task02.join();
//task03.join();
//不影響當前程式碼的執行,幾個執行緒後臺自行執行,不阻塞主執行緒
//task01.detach();
//task02.detach();
task03.detach();
//lambda匿名函式
for (int j=0;j<20;j++)
{
thread t([j]() {cout << j << endl; });
t.detach();
}
for (int i = 0; i<10; i++)
{
cout << "main thread's " << i << endl;
Sleep(200);
}
cout << "task01.get_id() = " << task01.get_id() << endl;//獲取執行緒ID
cin.get();
return 0;
}