您好,登錄后才能下訂單哦!
這篇文章主要講解了“C++初級線程使用方法有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“C++初級線程使用方法有哪些”吧!
C++11中,線程的啟動終究是對std::thread
的對象進行構造。
線程構造的類別如下:
此類可以說是最簡單的線程啟動,函數不需要傳參也不需要返回函數執行結果,執行完成后,線程自動退出。
形如:
void FunDoingNothing(); std::thread(FunDoingNothing)
編寫代碼時,需要加上<thread>頭文件以方便編譯器能夠正確處理thread對象。
C+=11中,thread的構造函數中使用了可變參數,這樣,可以使得構造thread
對象時可以自定義傳入參數,
構造函數的定義如下:
template<class F, class... Args> explicit thread(F&& f, Args&&... args);
在實際使用時,線程函數有參數時可以定義形式如下:
void printMsg(int a, int b) { cout << "input params are:" << a <<","<<b<< endl; } std::thread my_thread(printMsg, 3, 4)
使用時,可以將帶有執行函數的變量傳入thread
的構造函數中從而替換默認的構造函數,
如下:
using namespace std; class BackGroundTask{ public: void operator()() const{ doSomeThing(); } priavte: doSomeThing(); }; int main(){ BackGroundTask f; std::thread myThread(f); }
上面的代碼中,在啟動線程時同構構造對象f,f對象的重載函數中調用了線程運行時要執行的方法。但有一點需要注意的是,在傳入臨時的構造對象時,不經過處理,可能會讓編譯器產生錯誤的理解。
如:
std::thread myThread(BackGroundTask());
這里相當與聲明了一個名為myTread
的函數, 這個函數帶有一個參數(函數指針指向沒有參數并返回BackGroundTask
對象的函數), 返回一個 std::thread
對象的函數, 而非啟動了一個線程。
如果要解決這個問題,只需要如下處理即可:
std::thread myThread((BackGroundTask())); std::thread myThread{BackGroundTask()};
當然,也可以使用lamda表達式實現上述功能,如下:
std::thread myThread([]{ doSomeThing(); });
C++11中,確保線程執行完后,主線程在退出,需要在代碼中使用join()函數,這樣就可以保證變量在線程結束時才會進行銷毀。
在實際編程時,join
函數只是簡單的等待或者不等待。在有些場景下就會不使用,如果想要進行更加靈活的控制,需要使用C++11中提供的其他機制,這個也會在后面的推文中進行說明。
在編程時,如果對一個線程使用了join,那么在后續的操作中如果使用joinable()
執行結果將返回false
。既一旦使用了join。線程對象將不能重復使用。如下代碼中,在線程中使用join等待。
class BackGroundTask { public: void operator()() { doSomeThing(); } private: void doSomeThing() {cout<<"線程退出"<<endl;}; }; int main() { BackGroundTask f; std::thread myThread(f); myThread.join(); cout<<"退出"<<endl; }
上面的代碼使用了線程等待,可以輸出正確的結果,如下:
線程退出
退出
如果將 myThread.join()
語句注釋,再次執行時,程序將執行出錯,因為在子線程還沒有結束時,主線程已經結束。
運行結果如下:
退出
terminate called without an active exception
上面的輸出具備不確定性,代碼運行時結果隨機。
異常場景中,如果沒有充分考慮join的位置,就可能會產生因為異常導致主線程先于子線程退出的情況,解決這些問題可以通過下面兩種方法進行處理:
通過分析代碼中的異常場景,對異常使用try...catch
進行捕獲,然后在需要線程等待的地方調用join()
函數,這種方法雖然可以輕易地捕獲問題并對問題進行修復,但并非是通用法則,還需要根據實際情況進行分析。如檢查并確認是否線程函數中是否使用了局部變量的引用等其它原因。
RAII可以理解為資源獲取既初始化。因為全寫為:Resource Acquisition Is Initialization
。
實際使用時,通過定義一個類,然后在析構函數中使用join函數進行線程等待。這樣可以避免場景有遺漏的地方。
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
類對象的局部變量被銷毀,則在析構函數中會將線程托管到原始線程。
在thread_guard
中,使用delete
標識,禁止生成該類的默認拷貝構造、以及賦值函數。
在實際編程時如果不想線程等待,可以使用detach
方法,將線程和主線程進行分離。
線程分離使用detach
方法,使用后將不能在對已分離的線程進行管理,但是分離的線程可以真實的在后臺進行運行。當線程退出時,C++會對線程資源進行清理和回收。
線程分離通常被用作守護線程或者后臺工作線程。
使用方法如下:
int main() { BackGroundTask f; std::thread myThread(f); myThread.detach(); cout<<"退出"<<endl; }
向線程傳遞參數非常簡單,在上面的代碼中也有提及,這里主要說下向線程中傳遞參數的陷阱。
看下面的代碼:
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
是一個局部指針變量,使用后,可能會導致線程出現未定義的行為,因為從char*到string
的轉換時使用的是隱式轉換,但是thread在使用時會將變量拷貝到線程私有內存,但是并不知道需要將參數進行轉換,因此復制到私有內存的變量就沒有轉換成期望的對象。
如果要解決這個問題,可以在使用時直接將參數類型轉換成函數默認的類型,在上面的例子中可以
做如下操作:
std::thread t(f,3,std::string(buffer));
但是這樣做依然存在問題,既線程在復制變量到私有內存時,只復制了變量值,這樣在線程調用后,如果繼續使用線程函數處理后的變量時可能變量并沒有改造,依舊是線程調用之前的變量。
因此要想在函數傳參過程中使得線程拷貝時依舊保持引用,可以在線程調用時使用引用方式,
如:
std::thread t(f,3,std::ref(std::string(buffer)));
每個線程都有一個線程標識,在C++11中,線程標識通過std::thread::id
進行標識,std::thread::id
可以復用并進行比較,如果兩個線程的id相等,那么它們就是同一個線程或者沒有線程,如果不等就表示兩個是不同的線程或者其中一個線程不存在。
線程id的獲取方法有兩種,如下:
通過std::thread::get_id()
可以獲取線程的id。
使用方法如下:
int main() { BackGroundTask f; std::thread myThread(f); cout<<"線程id:"<<myThread.get_id()<<endl; myThread.detach(); cout<<"退出"<<endl; }
線程運行結果為:
線程id:139879559096064
退出
線程id可以用來區分主線程和子線程,通過std::this_thread::get_id()
可以先將主線程id保存,然后在和子線程進行比較,從而區分主線程和子線程。
代碼如下:
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標識,然后獲取子線程id,比較兩個線程id。如果不相等則輸出子線程id。
代碼運行結果如下:
子線程id:140161423791872
感謝各位的閱讀,以上就是“C++初級線程使用方法有哪些”的內容了,經過本文的學習后,相信大家對C++初級線程使用方法有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。