C++初級線程管理
前言:
實際程序運行時,每個程序都有一個程序入口,線程也不例外,使用線程時,需要給線程提供一個入口函數(shù),線程執(zhí)行完入口函數(shù)時,線程將退出。C++11中提供了std::thread庫,本文將從線程的啟動、線程等待、線程分離、線程傳參、線程識別等幾個方面介紹初級線程管理的知識。
1 線程啟動
C++11中,線程的啟動終究是對std::thread的對象進(jìn)行構(gòu)造。
線程構(gòu)造的類別如下:
1.1? 線程函數(shù)無參數(shù)無返回值
此類可以說是最簡單的線程啟動,函數(shù)不需要傳參也不需要返回函數(shù)執(zhí)行結(jié)果,執(zhí)行完成后,線程自動退出。
形如:
void FunDoingNothing(); std::thread(FunDoingNothing)
編寫代碼時,需要加上<thread>頭文件以方便編譯器能夠正確處理thread對象。
1.2? 線程函數(shù)有參數(shù)無返回值
C+=11中,thread的構(gòu)造函數(shù)中使用了可變參數(shù),這樣,可以使得構(gòu)造thread對象時可以自定義傳入?yún)?shù),
構(gòu)造函數(shù)的定義如下:
template<class F, class... Args> explicit thread(F&& f, Args&&... args);
在實際使用時,線程函數(shù)有參數(shù)時可以定義形式如下:
void printMsg(int a, int b) {
cout << "input params are:" << a <<","<<b<< endl;
}
std::thread my_thread(printMsg, 3, 4)
1.3? 調(diào)用可調(diào)用的類型構(gòu)造
使用時,可以將帶有執(zhí)行函數(shù)的變量傳入thread的構(gòu)造函數(shù)中從而替換默認(rèn)的構(gòu)造函數(shù),
如下:
using namespace std;
class BackGroundTask{
public:
void operator()() const{
doSomeThing();
}
priavte:
doSomeThing();
};
int main(){
BackGroundTask f;
std::thread myThread(f);
}
上面的代碼中,在啟動線程時同構(gòu)構(gòu)造對象f,f對象的重載函數(shù)中調(diào)用了線程運行時要執(zhí)行的方法。但有一點需要注意的是,在傳入臨時的構(gòu)造對象時,不經(jīng)過處理,可能會讓編譯器產(chǎn)生錯誤的理解。
如:
std::thread myThread(BackGroundTask());
這里相當(dāng)與聲明了一個名為myTread的函數(shù), 這個函數(shù)帶有一個參數(shù)(函數(shù)指針指向沒有參數(shù)并返回BackGroundTask對象的函數(shù)), 返回一個 std::thread 對象的函數(shù), 而非啟動了一個線程。
如果要解決這個問題,只需要如下處理即可:
std::thread myThread((BackGroundTask()));
std::thread myThread{BackGroundTask()};
當(dāng)然,也可以使用lamda表達(dá)式實現(xiàn)上述功能,如下:
std::thread myThread([]{
doSomeThing();
});
2 等待線程
C++11中,確保線程執(zhí)行完后,主線程在退出,需要在代碼中使用join()函數(shù),這樣就可以保證變量在線程結(jié)束時才會進(jìn)行銷毀。
2.1 join等待
在實際編程時,join函數(shù)只是簡單的等待或者不等待。在有些場景下就會不使用,如果想要進(jìn)行更加靈活的控制,需要使用C++11中提供的其他機(jī)制,這個也會在后面的推文中進(jìn)行說明。
在編程時,如果對一個線程使用了join,那么在后續(xù)的操作中如果使用joinable()執(zhí)行結(jié)果將返回false。既一旦使用了join。線程對象將不能重復(fù)使用。如下代碼中,在線程中使用join等待。
class BackGroundTask
{
public:
void operator()()
{
doSomeThing();
}
private:
void doSomeThing() {cout<<"線程退出"<<endl;};
};
int main()
{
BackGroundTask f;
std::thread myThread(f);
myThread.join();
cout<<"退出"<<endl;
}
上面的代碼使用了線程等待,可以輸出正確的結(jié)果,如下:
線程退出
退出
如果將 myThread.join()語句注釋,再次執(zhí)行時,程序?qū)?zhí)行出錯,因為在子線程還沒有結(jié)束時,主線程已經(jīng)結(jié)束。
運行結(jié)果如下:
退出
terminate called without an active exception
上面的輸出具備不確定性,代碼運行時結(jié)果隨機(jī)。
2.2 異常場景的join等待
異常場景中,如果沒有充分考慮join的位置,就可能會產(chǎn)生因為異常導(dǎo)致主線程先于子線程退出的情況,解決這些問題可以通過下面兩種方法進(jìn)行處理:
2.2.1? 通過異常捕獲
通過分析代碼中的異常場景,對異常使用try...catch進(jìn)行捕獲,然后在需要線程等待的地方調(diào)用join()函數(shù),這種方法雖然可以輕易地捕獲問題并對問題進(jìn)行修復(fù),但并非是通用法則,還需要根據(jù)實際情況進(jìn)行分析。如檢查并確認(rèn)是否線程函數(shù)中是否使用了局部變量的引用等其它原因。
2.2.2 使用RAII方式進(jìn)行線程等待
RAII可以理解為資源獲取既初始化。因為全寫為:Resource Acquisition Is Initialization。
實際使用時,通過定義一個類,然后在析構(gòu)函數(shù)中使用join函數(shù)進(jìn)行線程等待。這樣可以避免場景有遺漏的地方。
class thread_guard
{
private:
std::thread& t;
public:
explicit thread_guard(std::thread& t_):t(t_){}
~thread_guard()
{
if(t.joinable())
{
t.join();
}
}
thread_guard(thread_guard const&)=delete;
thread_guard& operator=(thread_guard const&)=delete;
};
?如上,通過在將線程對象傳入到類thread_guard中,如果thread_guard類對象的局部變量被銷毀,則在析構(gòu)函數(shù)中會將線程托管到原始線程。
在thread_guard中,使用delete標(biāo)識,禁止生成該類的默認(rèn)拷貝構(gòu)造、以及賦值函數(shù)。
在實際編程時如果不想線程等待,可以使用detach方法,將線程和主線程進(jìn)行分離。
3 線程分離
線程分離使用detach方法,使用后將不能在對已分離的線程進(jìn)行管理,但是分離的線程可以真實的在后臺進(jìn)行運行。當(dāng)線程退出時,C++會對線程資源進(jìn)行清理和回收。
線程分離通常被用作守護(hù)線程或者后臺工作線程。
使用方法如下:
int main()
{
BackGroundTask f;
std::thread myThread(f);
myThread.detach();
cout<<"退出"<<endl;
}
4 向線程傳遞參數(shù)
向線程傳遞參數(shù)非常簡單,在上面的代碼中也有提及,這里主要說下向線程中傳遞參數(shù)的陷阱。
看下面的代碼:
void f(int i,std::string const& s);
void oops(int some_param)
{
char buffer[1024];
sprintf(buffer, "%i",some_param);
std::thread t(f,3,buffer);
t.detach();
}
上面的代碼中buffer是一個局部指針變量,使用后,可能會導(dǎo)致線程出現(xiàn)未定義的行為,因為從char*到string的轉(zhuǎn)換時使用的是隱式轉(zhuǎn)換,但是thread在使用時會將變量拷貝到線程私有內(nèi)存,但是并不知道需要將參數(shù)進(jìn)行轉(zhuǎn)換,因此復(fù)制到私有內(nèi)存的變量就沒有轉(zhuǎn)換成期望的對象。
如果要解決這個問題,可以在使用時直接將參數(shù)類型轉(zhuǎn)換成函數(shù)默認(rèn)的類型,在上面的例子中可以
做如下操作:
std::thread t(f,3,std::string(buffer));
但是這樣做依然存在問題,既線程在復(fù)制變量到私有內(nèi)存時,只復(fù)制了變量值,這樣在線程調(diào)用后,如果繼續(xù)使用線程函數(shù)處理后的變量時可能變量并沒有改造,依舊是線程調(diào)用之前的變量。
因此要想在函數(shù)傳參過程中使得線程拷貝時依舊保持引用,可以在線程調(diào)用時使用引用方式,
如:
std::thread t(f,3,std::ref(std::string(buffer)));
5 線程識別
每個線程都有一個線程標(biāo)識,在C++11中,線程標(biāo)識通過std::thread::id進(jìn)行標(biāo)識,std::thread::id可以復(fù)用并進(jìn)行比較,如果兩個線程的id相等,那么它們就是同一個線程或者沒有線程,如果不等就表示兩個是不同的線程或者其中一個線程不存在。
線程id的獲取方法有兩種,如下:
5.1 thread成員函數(shù)獲取
通過std::thread::get_id()可以獲取線程的id。
使用方法如下:
int main()
{
BackGroundTask f;
std::thread myThread(f);
cout<<"線程id:"<<myThread.get_id()<<endl;
myThread.detach();
cout<<"退出"<<endl;
}
線程運行結(jié)果為:
- 線程id:139879559096064
- 退出
5.2 std::this_thread::get_id()
線程id可以用來區(qū)分主線程和子線程,通過std::this_thread::get_id()可以先將主線程id保存,然后在和子線程進(jìn)行比較,從而區(qū)分主線程和子線程。
代碼如下:
int main()
{
std::thread::id master_thread=std::this_thread::get_id();
BackGroundTask f;
std::thread myThread(f);
if(master_thread!=myThread.get_id())
{
cout<<"子線程id:"<<myThread.get_id()<<endl;
}
myThread.detach();
cout<<"退出"<<endl;
}
代碼中,先保存了主線程的id標(biāo)識,然后獲取子線程id,比較兩個線程id。如果不相等則輸出子線程id。
代碼運行結(jié)果如下:
子線程id:140161423791872
到此這篇關(guān)于C++初級線程管理的文章就介紹到這了,更多相關(guān)C++線程管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++類重載函數(shù)的function和bind使用示例
這篇文章主要介紹了C++類重載函數(shù)的function和bind使用示例,幫助大家更好的理解和使用c++,感興趣的朋友可以了解下2021-01-01
C++結(jié)構(gòu)體struct和類class區(qū)別詳解
struct和class有什么區(qū)別?最本質(zhì)的一個區(qū)別就是默認(rèn)的訪問控制:默認(rèn)的繼承訪問權(quán)限,struct是public的,class是private的。2017-11-11
Qt Design Studio創(chuàng)建工程的實現(xiàn)方法
Qt Design Studio它允許設(shè)計人員和開發(fā)人員使用通用的設(shè)計、開發(fā)、分析和調(diào)試工具在不同的開發(fā)平臺上共享一個項目,本文主要介紹了Qt Design Studio創(chuàng)建工程的實現(xiàn)方法,具有一定的參考價值,感興趣的可以了解一下2022-05-05
VC++ loadlibrary()加載三方dll失敗, 返回錯誤碼:126的解決方法
今天在編寫VC++ loadlibrary()加載三方dll是總是失敗,并且返回錯誤碼:126,這里就為大家分享一下具體的解決方法2021-03-03

