C++多線程編程超詳解
C++多線程
1. 概念
- 進(jìn)程:一個(gè)在內(nèi)存中運(yùn)行的應(yīng)用程序。每個(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程可以有多個(gè)線程,比如在Windows系統(tǒng)中,一個(gè)運(yùn)行的xx.exe就是一個(gè)進(jìn)程。
- 線程:進(jìn)程中的一個(gè)執(zhí)行任務(wù)(控制單元),負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行。一個(gè)進(jìn)程至少有一個(gè)線程,一個(gè)進(jìn)程可以運(yùn)行多個(gè)線程,多個(gè)線程可共享數(shù)據(jù)。與進(jìn)程不同的是同類的多個(gè)線程共享進(jìn)程的堆和方法區(qū)資源,但每個(gè)線程有自己的程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧,所以系統(tǒng)在產(chǎn)生一個(gè)線程,或是在各個(gè)線程之間作切換工作時(shí),負(fù)擔(dān)要比進(jìn)程小得多,也正因?yàn)槿绱?,線程也被稱為輕量級進(jìn)程。
- 并發(fā):并發(fā)指的是兩個(gè)或多個(gè)獨(dú)立的活動在同一時(shí)段內(nèi)發(fā)生。并發(fā)在生活中隨處可見:比如在跑步的時(shí)候同時(shí)聽音樂,在看電腦顯示器的同時(shí)敲擊鍵盤等。同一時(shí)間段內(nèi)可以交替處理多個(gè)操作,強(qiáng)調(diào)同一時(shí)段內(nèi)交替發(fā)生。
- 并行:同一時(shí)刻內(nèi)同時(shí)處理多個(gè)操作,強(qiáng)調(diào)同一時(shí)刻點(diǎn)同時(shí)發(fā)生。
2. 常用API
頭文件#include<thread>
1.thread
API | 描述 | 注意 |
---|---|---|
thread.join() | 加入線程(會阻塞主線程,模擬同步操作) | |
thread.detach() | 加入線程(不會阻塞主線程,模擬異步操作) | |
thread.joinable() | 是否可加入線程,返回bool | |
thread.get_id() | 獲取線程的ID | |
thread.hardware_concurrency() | 獲取硬件并發(fā)的數(shù)量 | |
thread.swap() | 交換線程 | |
thread.native_handle() | 獲取原生handle,為windows多線程中CreateThread的返回值,使用這個(gè)handle從而可以實(shí)現(xiàn)線程的掛起喚醒 |
測試代碼:
void threadFunc01() { cout << "thread join1" << endl; this_thread::sleep_for(chrono::seconds(2)); } void threadFunc02() { cout << "thread join2" << endl; this_thread::sleep_for(chrono::seconds(2)); } void test01() { // 創(chuàng)建線程 std::thread thread1(threadFunc01); std::thread thread2(threadFunc02); //thread.join(); //join 會阻塞主線程 同步操作 //thread.detach(); //detach 不會阻塞主線程 異步操作 bool bJoinAble = thread1.joinable(); thread::id threadId = thread1.get_id(); //hardware_concurrency 硬件并發(fā)的數(shù)量 int threadNum = thread1.hardware_concurrency(); cout << "hardware_concurrency:" << threadNum << endl; //應(yīng)用 線程的預(yù)分配。 for (int i = 0; i < thread1.hardware_concurrency(); i++) { std::thread threadRef(threadFunc01); threadRef.detach(); } thread1.swap(thread2); thread1.join(); }
向線程里傳遞參數(shù)的方法:
// 向線程里傳遞參數(shù)的方法 #include<string> void threadFunc03(int num, const string& str) { cout << "num = " << num << " str = " << str << endl; } struct FObject { void Run(const string& str) { cout << str << endl; } }; void test02() { // 通過函數(shù)綁定 thread newThread1(threadFunc03, 10, "Unreal"); newThread1.detach(); // 通過lambda綁定 int a = 50; thread newThread2([&](int num,const string& str) { cout << "a = " << a << " num = " << num << " str = " << str << endl; }, 1, "Unreal"); newThread2.detach(); // 綁定對象 FObject objectRef; thread newThread3(&FObject::Run, objectRef, "Unreal"); newThread3.detach(); }
2.互斥鎖mutex
頭文件#include<mutex>
API | 描述 | 注意 |
---|---|---|
mutex.lock() | 上鎖 | |
mutex.unlock() | 解鎖 | |
mutex.try_lock() | 判斷可不可以加鎖,返回bool | 可以用該方法建立非阻塞模式 |
測試代碼:
#include<mutex> mutex lockRef; void threadFunc04(int num,const string& str) { // 進(jìn)入該線程鎖住該線程,其他線程想要進(jìn)入該線程需要排隊(duì) lockRef.lock(); cout << "thread join4" << endl; this_thread::sleep_for(chrono::seconds(2)); // 解鎖 lockRef.unlock(); } void test03() { std::thread thread1(threadFunc04, 10, "Unreal"); std::thread thread2(threadFunc04, 5, "Unity"); std::thread thread3(threadFunc04, 20, "Cocos"); thread1.detach(); thread2.detach(); thread3.detach(); }
使用類加鎖的方式:
#include<mutex> mutex lockRef; struct FEvent { FEvent() { m.lock(); } ~FEvent() { m.unlock(); } static mutex m; }; mutex FEvent::m; #define LOCK_SCOPE FEvent Event void threadFunc04(int num,const string& str) { LOCK_SCOPE; //加上鎖,并且過了這個(gè)作用域自動解鎖(析構(gòu)) cout << "thread join4" << endl; this_thread::sleep_for(chrono::seconds(2)); } void test03() { std::thread thread1(threadFunc04, 10, "Unreal"); std::thread thread2(threadFunc04, 5, "Unity"); std::thread thread3(threadFunc04, 20, "Cocos"); thread1.detach(); thread2.detach(); thread3.detach(); }
try_lock()
void threadFunc04(int num,const string& str) { bool bLock = FEvent::m.try_lock(); if (bLock) { LOCK_SCOPE; //加上鎖,并且過了這個(gè)作用域自動解鎖(析構(gòu)) cout << "thread join4" << endl; this_thread::sleep_for(chrono::seconds(2)); } }
使用try_lock()可以進(jìn)行判斷能不能上鎖,不能上鎖的話,就不用執(zhí)行上鎖后的代碼,防止其他線程阻塞在該線程。
lock_guard
lock_guard是一種鎖類,作用和我們上面自定義的鎖類FEvent相同,創(chuàng)建的時(shí)候鎖住目標(biāo)線程,釋放的時(shí)候解鎖。
// 聲明方式 lock_guard<mutex>ref;
源碼:
template <class _Mutex> class lock_guard { // class with destructor that unlocks a mutex public: using mutex_type = _Mutex; explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock _MyMutex.lock(); } lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) { // construct but don't lock } ~lock_guard() noexcept { _MyMutex.unlock(); } lock_guard(const lock_guard&) = delete; lock_guard& operator=(const lock_guard&) = delete; private: _Mutex& _MyMutex; };
unique_lock
作用和lock_guard
相同,唯一的不同之處,lock_guard
開放的API只有析構(gòu)函數(shù),而unique_lock
開放的API非常多,即自由度比lock_guard
高,可以定義鎖的行為。
void test05() { // defer_lock 關(guān)鍵字為延遲鎖,即創(chuàng)建該對象時(shí)不會鎖住該線程,什么時(shí)候鎖需要自定義 std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock); std::unique_lock<mutex>lockRef2(FEvent::m,chrono::seconds(2)); //鎖兩秒 //....執(zhí)行 lockRef2.lock(); lockRef2.unlock(); bool bLock1 = lockRef2.try_lock();//嘗試上鎖 lockRef2.try_lock_for(chrono::seconds(2)); //鎖2s mutex *lockRef3 = lockRef2.release(); //釋放鎖,同時(shí)會返回被釋放的這個(gè)鎖的指針對象 bool bLock2 = lockRef2.owns_lock(); //當(dāng)前是否被鎖住 }
應(yīng)用:
void test05() { //std::lock_guard<mutex>lockRef1(FEvent::m); // defer_lock 關(guān)鍵字為延遲鎖 std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock); lockRef2.lock(); lockRef2.mutex(); bool bLock = lockRef2.owns_lock(); std::unique_lock<mutex>lockRef3; lockRef2.swap(lockRef3); std::unique_lock<mutex>lockRef4 = move(lockRef3); lockRef4.unlock(); }
3. 掛起和喚醒
頭文件#include<windows.h>
1111111
111111
111111
11111
111111
測試代碼:
#include<windows.h> void threadFunc05() { while (true) { Sleep(10); cout << "threadFunc05" << endl; } } void test04() { thread thread1(threadFunc05); // 掛起線程 SuspendThread(thread1.native_handle()); Sleep(2); // 喚醒線程 ResumeThread(thread1.native_handle()); }
如何高效將主線程資源進(jìn)行轉(zhuǎn)移:
void threadFunc06(const char* str) { cout << str << endl; } void test04() { // 如何高效轉(zhuǎn)移線程資源 // 使用std::move thread thread2(threadFunc06, move("Unreal")); // 使用move避免了拷貝 thread thread3 = move(thread2); thread3.detach(); }
3. 應(yīng)用場景
3.1 call_once執(zhí)行一次的函數(shù)
通過使用該函數(shù),用來防止多線程的多次觸發(fā)。
once_flag tag; void callonceTest() { call_once(tag, [&]() { cout << "Do once" << endl; }); } void test06() { for (int i = 0; i < 10; i++) { thread thread1(callonceTest); thread1.detach(); } }
3.2 condition_variable條件鎖
使用需要包含頭文件#include<condition_variable>
可以使用條件鎖來達(dá)到同步的作用,即當(dāng)滿足一定的條件后才解鎖某個(gè)線程。
#include<condition_variable> condition_variable condition_lock; mutex mutexLock; void conditionFuncTest() { unique_lock<mutex>lock(mutexLock); condition_lock.wait(lock); //鎖住該線程 cout << "Run" << endl; } void test12() { std::thread threadRef(conditionFuncTest); threadRef.detach(); Sleep(3000); //3s后再激活 condition_lock.notify_one(); }
3.3 future獲取線程的計(jì)算結(jié)果
通過使用future可以得到"未來"線程被調(diào)用的時(shí)候計(jì)算得返回值,使用時(shí)需要包含頭文件#include<future>。
聲明方式:
// async為創(chuàng)建該線程的方式為異步 funName 函數(shù)名 args為傳入的函數(shù)參數(shù) std::future<string>newFuture = std::async(launch::async, funName,args...);
應(yīng)用:
#include<future> string getString(int num) { return "Unreal"; } void test08() { std::future<string>newFuture = std::async(launch::async, getString, 10); //std::future<string>newFuture = std::async(launch::deferred, getString, 10); // 睡一秒再執(zhí)行 Sleep(1000); string str = newFuture.get(); //get只能調(diào)用一次 調(diào)第二次會崩潰 // 防止崩潰的寫法 if (newFuture.valid()) { string str = newFuture.get(); } }
3.4 promise主線程如何將數(shù)據(jù)發(fā)送數(shù)據(jù)到其他線程
通過使用promise(承諾)來進(jìn)行進(jìn)程之間的交互,常配合std::future使用。其作用是在一個(gè)線程t1中保存一個(gè)類型typename T的值,可供相綁定的std::future對象在另一線程t2中獲取。
測試代碼:
// promise string promiseTest(future<string>& future) { cout << future.get() << endl; return "Unreal"; } void test09() { promise<string> promiseRef; future<string>future1 = promiseRef.get_future(); future<string>future2 = std::async(launch::async, promiseTest, std::ref(future1)); //future 不支持值拷貝 需要傳遞引用 promiseRef.set_value("Unreal is the best game engine in the world"); }
但這里也有一個(gè)問題需要思考,如果需要發(fā)送數(shù)據(jù)到多個(gè)線程,是不是需要一個(gè)個(gè)的創(chuàng)建上面的代碼呢。這里就引出了多線程之間共享狀態(tài)這個(gè)解決方法。
3.5 future.share()多線程之間共享狀態(tài)
通過future.share()我們可以很方便的使多個(gè)線程之間共享狀態(tài)。
現(xiàn)在來看看沒有使用該函數(shù)的話我們要共享狀態(tài)的話需要這么寫:
string promiseTest(future<string>& future) { cout << future.get() << endl; return "Unreal"; } void test09() { promise<string> promiseRef; future<string>future1 = promiseRef.get_future(); future<string>future2 = promiseRef.get_future(); future<string>future3 = promiseRef.get_future(); future<string>future4 = std::async(launch::async, promiseTest, std::ref(future1)); //future 不支持值拷貝 需要傳遞引用 future<string>future5 = std::async(launch::async, promiseTest, std::ref(future2)); //future 不支持值拷貝 需要傳遞引用 future<string>future6 = std::async(launch::async, promiseTest, std::ref(future3)); //future 不支持值拷貝 需要傳遞引用 promiseRef.set_value("Unreal is the best game engine in the world"); }
使用了future.share()
函數(shù)后:
string promiseTest02(shared_future<string> future) { cout << future.get() << endl; return "Unreal"; } void test09() { promise<string> promiseRef; future<string>future1 = promiseRef.get_future(); // shared_future shared_future<string> sharedFutrue1 = future1.share(); future<string>future2 = std::async(launch::async, promiseTest02, sharedFutrue1); //shared_future 可以用拷貝傳遞 future<string>future3 = std::async(launch::async, promiseTest02, sharedFutrue1); future<string>future4 = std::async(launch::async, promiseTest02, sharedFutrue1); promiseRef.set_value("Unreal is the best game engine in the world"); }
3.6 線程packaged_task
packaged_task
和promise
非常相似,packaged_task<F>
是對promise<T= std::function<F>>中T= std::function<F>
這一可調(diào)對象(如函數(shù)、lambda表達(dá)式等)進(jìn)行了包裝,簡化了使用方法。并將這一可調(diào)對象的返回結(jié)果傳遞給關(guān)聯(lián)的future對象。
綁定Lambda
void test10() { //綁定lambda packaged_task<int(int, int)> task1([](int a,int b) ->int{ return a + b; }); task1(1, 4); this_thread::sleep_for(chrono::seconds(1)); if (task1.valid()) { auto f1 = task1.get_future(); cout << f1.get() << endl; } }
綁定普通函數(shù)
int packagedTest(int a,int b) { return a + b; } void test10() { //綁定函數(shù) packaged_task<int(int, int)>task2(packagedTest); task2(10, 5); this_thread::sleep_for(chrono::seconds(1)); if (task2.valid()) { auto f2 = task2.get_future(); cout << f2.get() << endl; } }
使用std::bind進(jìn)行函數(shù)綁定
int packagedTest(int a,int b) { return a + b; } void test10() { // bind packaged_task<int(int, int)>task3(std::bind(packagedTest,1,2)); task3(10, 5); //因?yàn)閎ind使用了占位符 所以這里傳入的10 5失效了 this_thread::sleep_for(chrono::seconds(1)); if (task3.valid()) { auto f3 = task3.get_future(); cout << f3.get() << endl; //1+2 } }
3.7 時(shí)間約束
void test11() { //休眠2s this_thread::sleep_for(chrono::seconds(2)); // 休眠現(xiàn)在的時(shí)間加上2s chrono::steady_clock::time_point timePos = chrono::steady_clock::now() + chrono::seconds(2); this_thread::sleep_until(timePos); }
4. Windows多線程
使用WindowsAPI
進(jìn)行多線程的編寫,需要包含頭文件
#include<windows.h>
4.1 Windows創(chuàng)建線程
使用CreateThread()
創(chuàng)建線程
DWORD WINAPI funcThread(LPVOID lpPram) { // DWORD 類型為unsigned long // LPVOID 類型為void cout << "Unreal!" << endl; Sleep(1000); return 0l; } void windowsThreadTest01() { HANDLE handleRef = CreateThread(nullptr,0, funcThread,nullptr,0,nullptr); Sleep(2000); CloseHandle(handleRef); //使用之后需要關(guān)閉handle }
其中傳入的參數(shù)為:
/* WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, 和線程安全有關(guān) 一般為null _In_ SIZE_T dwStackSize, 線程棧的大小 _In_ LPTHREAD_START_ROUTINE lpStartAddress, 被線程執(zhí)行的回調(diào)函數(shù) _In_opt_ __drv_aliasesMem LPVOID lpParameter, 傳入線程的參數(shù) _In_ DWORD dwCreationFlags, 創(chuàng)建線程的標(biāo)志 參數(shù)0 代表立即啟動該線程 _Out_opt_ LPDWORD lpThreadId 傳出的線程ID ); */
4.2 Windows互斥鎖
// windows互斥鎖 HANDLE hMutex = nullptr; DWORD WINAPI funcThread02(LPVOID lpParam) { cout << "Unreal" << endl; WaitForSingleObject(hMutex, INFINITE); Sleep(5000); ReleaseMutex(hMutex); return 0l; } void windowsThreadTest02() { hMutex = CreateMutex(nullptr, false, L"Mutex"); HANDLE handleRef1 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr); HANDLE handleRef2 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr); CloseHandle(handleRef1); CloseHandle(handleRef2); }
傳入的參數(shù)為:
/* WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateMutexW( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, 和線程安全有關(guān)一般為null _In_ BOOL bInitialOwner, 有沒有該鎖的控制權(quán) _In_opt_ LPCWSTR lpName 鎖名字 ); */
4.3 Windows掛起和喚醒線程
通過使用SuspendThread(HandleRef)和ResumeThread(HandleRef)來掛起和喚醒線程
// windows 掛起喚醒 DWORD WINAPI funcThread03(LPVOID lpParam) { while (true) { Sleep(500); cout << "IsRunning" << endl; } return 0l; } void windowsThreadTest03() { HANDLE hRef = CreateThread(nullptr, 0, funcThread03, nullptr, 0, nullptr); SuspendThread(hRef); Sleep(2000); ResumeThread(hRef); CloseHandle(hRef); }
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
求32位機(jī)器上unsigned int的最大值及int的最大值的解決方法
本篇文章是對求32位機(jī)器上unsigned int的最大值及int的最大值的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05詳解基于C++實(shí)現(xiàn)約瑟夫環(huán)問題的三種解法
約瑟夫環(huán)問題是算法中相當(dāng)經(jīng)典的一個(gè)問題,其問題理解是相當(dāng)容易的,并且問題描述有非常多的版本,并且約瑟夫環(huán)問題還有很多變形,通過這篇約瑟夫問題的講解,一定可以帶你理解透徹2021-06-06基礎(chǔ)C語言編程時(shí)易犯錯(cuò)誤有哪些
基礎(chǔ)C語言編程時(shí)易犯錯(cuò)誤有哪些?這篇文章主要介紹了C語言編程時(shí)常見的錯(cuò)誤,感興趣的小伙伴們可以參考一下2016-11-11C語言實(shí)現(xiàn)修改文本文件中特定行的實(shí)現(xiàn)代碼
最近由于項(xiàng)目需要實(shí)現(xiàn)修改文件的功能,所以,博主認(rèn)真查閱了一些資料,但是,很遺憾,并沒有太多的收獲2013-06-06從匯編看c++的默認(rèn)析構(gòu)函數(shù)的使用詳解
本篇文章是對c++中默認(rèn)析構(gòu)函數(shù)的使用進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05C語言時(shí)間函數(shù)之mktime和difftime詳解
這篇文章主要為大家詳細(xì)介紹了C語言時(shí)間函數(shù)之mktime和difftime,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一,希望能夠給你帶來幫助2022-02-02