您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何理解C++多線程編程”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“如何理解C++多線程編程”吧!
C++多線程
1. 概念
1.1 概念
2. 常用API
1.thread
2.互斥鎖mutex
3. 掛起和喚醒
3. 應用場景
3.1 call_once執行一次的函數
3.2 condition_variable條件鎖
3.3 future獲取線程的計算結果
3.4 promise主線程如何將數據發送數據到其他線程
3.5 future.share()多線程之間共享狀態
3.6 線程packaged_task
3.7 時間約束
4. Windows多線程
4.1 Windows創建線程
4.2 Windows互斥鎖
4.3 Windows掛起和喚醒線程
總結
進程:一個在內存中運行的應用程序。每個進程都有自己獨立的一塊內存空間,一個進程可以有多個線程,比如在Windows系統中,一個運行的xx.exe就是一個進程。
線程:進程中的一個執行任務(控制單元),負責當前進程中程序的執行。一個進程至少有一個線程,一個進程可以運行多個線程,多個線程可共享數據。與進程不同的是同類的多個線程共享進程的堆和方法區資源,但每個線程有自己的程序計數器、虛擬機棧和本地方法棧,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。
并發:并發指的是兩個或多個獨立的活動在同一時段內發生。并發在生活中隨處可見:比如在跑步的時候同時聽音樂,在看電腦顯示器的同時敲擊鍵盤等。同一時間段內可以交替處理多個操作,強調同一時段內交替發生。
并行:同一時刻內同時處理多個操作,強調同一時刻點同時發生。
頭文件#include<thread>
API | 描述 | 注意 |
---|---|---|
thread.join() | 加入線程(會阻塞主線程,模擬同步操作) | |
thread.detach() | 加入線程(不會阻塞主線程,模擬異步操作) | |
thread.joinable() | 是否可加入線程,返回bool | |
thread.get_id() | 獲取線程的ID | |
thread.hardware_concurrency() | 獲取硬件并發的數量 | |
thread.swap() | 交換線程 | |
thread.native_handle() | 獲取原生handle,為windows多線程中CreateThread的返回值,使用這個handle從而可以實現線程的掛起喚醒 |
測試代碼:
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() { // 創建線程 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 硬件并發的數量 int threadNum = thread1.hardware_concurrency(); cout << "hardware_concurrency:" << threadNum << endl; //應用 線程的預分配。 for (int i = 0; i < thread1.hardware_concurrency(); i++) { std::thread threadRef(threadFunc01); threadRef.detach(); } thread1.swap(thread2); thread1.join(); }
向線程里傳遞參數的方法:
// 向線程里傳遞參數的方法 #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() { // 通過函數綁定 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(); }
頭文件#include<mutex>
API | 描述 | 注意 |
---|---|---|
mutex.lock() | 上鎖 | |
mutex.unlock() | 解鎖 | |
mutex.try_lock() | 判斷可不可以加鎖,返回bool | 可以用該方法建立非阻塞模式 |
測試代碼:
#include<mutex> mutex lockRef; void threadFunc04(int num,const string& str) { // 進入該線程鎖住該線程,其他線程想要進入該線程需要排隊 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; //加上鎖,并且過了這個作用域自動解鎖(析構) 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; //加上鎖,并且過了這個作用域自動解鎖(析構) cout << "thread join4" << endl; this_thread::sleep_for(chrono::seconds(2)); } }
使用try_lock()
可以進行判斷能不能上鎖,不能上鎖的話,就不用執行上鎖后的代碼,防止其他線程阻塞在該線程。
lock_guard
lock_guard
是一種鎖類,作用和我們上面自定義的鎖類FEvent
相同,創建的時候鎖住目標線程,釋放的時候解鎖。
// 聲明方式 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
只有析構函數,而unique_lock
開放的API非常多,即自由度比lock_guard
高,可以定義鎖的行為。
void test05() { // defer_lock 關鍵字為延遲鎖,即創建該對象時不會鎖住該線程,什么時候鎖需要自定義 std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock); std::unique_lock<mutex>lockRef2(FEvent::m,chrono::seconds(2)); //鎖兩秒 //....執行 lockRef2.lock(); lockRef2.unlock(); bool bLock1 = lockRef2.try_lock();//嘗試上鎖 lockRef2.try_lock_for(chrono::seconds(2)); //鎖2s mutex *lockRef3 = lockRef2.release(); //釋放鎖,同時會返回被釋放的這個鎖的指針對象 bool bLock2 = lockRef2.owns_lock(); //當前是否被鎖住 }
應用:
void test05() { //std::lock_guard<mutex>lockRef1(FEvent::m); // defer_lock 關鍵字為延遲鎖 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(); }
頭文件#include<windows.h>
API | 描述 | 注意 |
---|---|---|
SuspendThread(thread.native_hadle()) | 掛起線程 | |
ResumeThread(thread.native_hadle()) | 喚醒線程 | |
Sleep() | 睡眠 |
測試代碼:
#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()); }
如何高效將主線程資源進行轉移:
void threadFunc06(const char* str) { cout << str << endl; } void test04() { // 如何高效轉移線程資源 // 使用std::move thread thread2(threadFunc06, move("Unreal")); // 使用move避免了拷貝 thread thread3 = move(thread2); thread3.detach(); }
通過使用該函數,用來防止多線程的多次觸發。
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(); } }
使用需要包含頭文件#include<condition_variable>
可以使用條件鎖來達到同步的作用,即當滿足一定的條件后才解鎖某個線程。
#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(); }
通過使用future可以得到"未來"線程被調用的時候計算得返回值,使用時需要包含頭文件#include<future>。
聲明方式:
// async為創建該線程的方式為異步 funName 函數名 args為傳入的函數參數 std::future<string>newFuture = std::async(launch::async, funName,args...);
應用:
#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); // 睡一秒再執行 Sleep(1000); string str = newFuture.get(); //get只能調用一次 調第二次會崩潰 // 防止崩潰的寫法 if (newFuture.valid()) { string str = newFuture.get(); } }
通過使用promise(承諾)
來進行進程之間的交互,常配合std::future
使用。其作用是在一個線程t1中保存一個類型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"); }
但這里也有一個問題需要思考,如果需要發送數據到多個線程,是不是需要一個個的創建上面的代碼呢。這里就引出了多線程之間共享狀態這個解決方法。
通過future.share()我們可以很方便的使多個線程之間共享狀態。
現在來看看沒有使用該函數的話我們要共享狀態的話需要這么寫:
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()
函數后:
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"); }
packaged_task
和promise
非常相似,packaged_task<F>
是對promise<T= std::function<F>>中T= std::function<F>
這一可調對象(如函數、lambda
表達式等)進行了包裝,簡化了使用方法。并將這一可調對象的返回結果傳遞給關聯的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; } }
綁定普通函數
int packagedTest(int a,int b) { return a + b; } void test10() { //綁定函數 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
進行函數綁定
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); //因為bind使用了占位符 所以這里傳入的10 5失效了 this_thread::sleep_for(chrono::seconds(1)); if (task3.valid()) { auto f3 = task3.get_future(); cout << f3.get() << endl; //1+2 } }
void test11() { //休眠2s this_thread::sleep_for(chrono::seconds(2)); // 休眠現在的時間加上2s chrono::steady_clock::time_point timePos = chrono::steady_clock::now() + chrono::seconds(2); this_thread::sleep_until(timePos); }
使用WindowsAPI
進行多線程的編寫,需要包含頭文件
#include<windows.h>
使用CreateThread()
創建線程
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); //使用之后需要關閉handle }
其中傳入的參數為:
/* WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, 和線程安全有關 一般為null _In_ SIZE_T dwStackSize, 線程棧的大小 _In_ LPTHREAD_START_ROUTINE lpStartAddress, 被線程執行的回調函數 _In_opt_ __drv_aliasesMem LPVOID lpParameter, 傳入線程的參數 _In_ DWORD dwCreationFlags, 創建線程的標志 參數0 代表立即啟動該線程 _Out_opt_ LPDWORD lpThreadId 傳出的線程ID ); */
// 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); }
傳入的參數為:
/* WINBASEAPI _Ret_maybenull_ HANDLE WINAPI CreateMutexW( _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, 和線程安全有關一般為null _In_ BOOL bInitialOwner, 有沒有該鎖的控制權 _In_opt_ LPCWSTR lpName 鎖名字 ); */
通過使用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); }
感謝各位的閱讀,以上就是“如何理解C++多線程編程”的內容了,經過本文的學習后,相信大家對如何理解C++多線程編程這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。