C++多線程編程超詳解
C++多線程
1. 概念
- 進(jìn)程:一個在內(nèi)存中運行的應(yīng)用程序。每個進(jìn)程都有自己獨立的一塊內(nèi)存空間,一個進(jìn)程可以有多個線程,比如在Windows系統(tǒng)中,一個運行的xx.exe就是一個進(jìn)程。
- 線程:進(jìn)程中的一個執(zhí)行任務(wù)(控制單元),負(fù)責(zé)當(dāng)前進(jìn)程中程序的執(zhí)行。一個進(jìn)程至少有一個線程,一個進(jìn)程可以運行多個線程,多個線程可共享數(shù)據(jù)。與進(jìn)程不同的是同類的多個線程共享進(jìn)程的堆和方法區(qū)資源,但每個線程有自己的程序計數(shù)器、虛擬機棧和本地方法棧,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負(fù)擔(dān)要比進(jìn)程小得多,也正因為如此,線程也被稱為輕量級進(jìn)程。
- 并發(fā):并發(fā)指的是兩個或多個獨立的活動在同一時段內(nèi)發(fā)生。并發(fā)在生活中隨處可見:比如在跑步的時候同時聽音樂,在看電腦顯示器的同時敲擊鍵盤等。同一時間段內(nèi)可以交替處理多個操作,強調(diào)同一時段內(nèi)交替發(fā)生。
- 并行:同一時刻內(nèi)同時處理多個操作,強調(diào)同一時刻點同時發(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的返回值,使用這個handle從而可以實現(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)入該線程需要排隊
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ò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òu))
cout << "thread join4" << endl;
this_thread::sleep_for(chrono::seconds(2));
}
}
使用try_lock()可以進(jìn)行判斷能不能上鎖,不能上鎖的話,就不用執(zhí)行上鎖后的代碼,防止其他線程阻塞在該線程。
lock_guard
lock_guard是一種鎖類,作用和我們上面自定義的鎖類FEvent相同,創(chuàng)建的時候鎖住目標(biāo)線程,釋放的時候解鎖。
// 聲明方式 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)建該對象時不會鎖住該線程,什么時候鎖需要自定義
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(); //釋放鎖,同時會返回被釋放的這個鎖的指針對象
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)滿足一定的條件后才解鎖某個線程。
#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獲取線程的計算結(jié)果
通過使用future可以得到"未來"線程被調(diào)用的時候計算得返回值,使用時需要包含頭文件#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使用。其作用是在一個線程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");
}
但這里也有一個問題需要思考,如果需要發(fā)送數(shù)據(jù)到多個線程,是不是需要一個個的創(chuàng)建上面的代碼呢。這里就引出了多線程之間共享狀態(tài)這個解決方法。
3.5 future.share()多線程之間共享狀態(tài)
通過future.share()我們可以很方便的使多個線程之間共享狀態(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); //因為bind使用了占位符 所以這里傳入的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 時間約束
void test11() {
//休眠2s
this_thread::sleep_for(chrono::seconds(2));
// 休眠現(xiàn)在的時間加上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位機器上unsigned int的最大值及int的最大值的解決方法
本篇文章是對求32位機器上unsigned int的最大值及int的最大值的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
詳解基于C++實現(xiàn)約瑟夫環(huán)問題的三種解法
約瑟夫環(huán)問題是算法中相當(dāng)經(jīng)典的一個問題,其問題理解是相當(dāng)容易的,并且問題描述有非常多的版本,并且約瑟夫環(huán)問題還有很多變形,通過這篇約瑟夫問題的講解,一定可以帶你理解透徹2021-06-06
C語言實現(xiàn)修改文本文件中特定行的實現(xiàn)代碼
最近由于項目需要實現(xiàn)修改文件的功能,所以,博主認(rèn)真查閱了一些資料,但是,很遺憾,并沒有太多的收獲2013-06-06
從匯編看c++的默認(rèn)析構(gòu)函數(shù)的使用詳解
本篇文章是對c++中默認(rèn)析構(gòu)函數(shù)的使用進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05
C語言時間函數(shù)之mktime和difftime詳解
這篇文章主要為大家詳細(xì)介紹了C語言時間函數(shù)之mktime和difftime,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一,希望能夠給你帶來幫助2022-02-02

