溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解C++多線程編程

發(fā)布時間:2021-09-28 10:45:08 來源:億速云 閱讀:154 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“如何理解C++多線程編程”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何理解C++多線程編程”吧!

目錄
  • C++多線程

  • 1. 概念

    • 1.1 概念

  • 2. 常用API

    • 1.thread

    • 2.互斥鎖mutex

    • 3. 掛起和喚醒

  • 3. 應(yīng)用場景

    • 3.1 call_once執(zhí)行一次的函數(shù)

    • 3.2 condition_variable條件鎖

    • 3.3 future獲取線程的計算結(jié)果

    • 3.4 promise主線程如何將數(shù)據(jù)發(fā)送數(shù)據(jù)到其他線程

    • 3.5 future.share()多線程之間共享狀態(tài)

    • 3.6 線程packaged_task

    • 3.7 時間約束

  • 4. Windows多線程

    • 4.1 Windows創(chuàng)建線程

    • 4.2 Windows互斥鎖

    • 4.3 Windows掛起和喚醒線程

  • 總結(jié)

C++多線程

1. 概念

1.1 概念

  • 進程:一個在內(nèi)存中運行的應(yīng)用程序。每個進程都有自己獨立的一塊內(nèi)存空間,一個進程可以有多個線程,比如在Windows系統(tǒng)中,一個運行的xx.exe就是一個進程。

  • 線程:進程中的一個執(zhí)行任務(wù)(控制單元),負責(zé)當(dāng)前進程中程序的執(zhí)行。一個進程至少有一個線程,一個進程可以運行多個線程,多個線程可共享數(shù)據(jù)。與進程不同的是同類的多個線程共享進程的堆和方法區(qū)資源,但每個線程有自己的程序計數(shù)器、虛擬機棧和本地方法棧,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負擔(dā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) {
	// 進入該線程鎖住該線程,其他線程想要進入該線程需要排隊
	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()可以進行判斷能不能上鎖,不能上鎖的話,就不用執(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>

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());
}

如何高效將主線程資源進行轉(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ā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(承諾)來進行進程之間的交互,常配合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_taskpromise非常相似,packaged_task<F>是對promise<T= std::function<F>>中T= std::function<F>這一可調(diào)對象(如函數(shù)、lambda表達式等)進行了包裝,簡化了使用方法。并將這一可調(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進行函數(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進行多線程的編寫,需要包含頭文件

#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);
}

感謝各位的閱讀,以上就是“如何理解C++多線程編程”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對如何理解C++多線程編程這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI