2

C++多线程编程

 2 years ago
source link: https://blog.csdn.net/weixin_45389639/article/details/120519176
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

C++多线程

1.1 概念

  • 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
  • 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
  • 并发:并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
  • 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。

2. 常用API

​ 头文件#include<thread>

1.thread

API描述注意thread.join()加入线程(会阻塞主线程,模拟同步操作)thread.detach()加入线程(不会阻塞主线程,模拟异步操作)thread.joinable()是否可加入线程,返回boolthread.get_id()获取线程的IDthread.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();
}

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; //加上锁,并且过了这个作用域自动解锁(析构)
	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();
}

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

如何高效将主线程资源进行转移:

void threadFunc06(const char* str) {
	cout << str << endl;
}

void test04() {
	// 如何高效转移线程资源
	// 使用std::move
	thread thread2(threadFunc06, move("Unreal")); // 使用move避免了拷贝
	thread thread3 = move(thread2);
	thread3.detach();
}

3. 应用场景

3.1 call_once执行一次的函数

​ 通过使用该函数,用来防止多线程的多次触发。

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>

可以使用条件锁来达到同步的作用,即当满足一定的条件后才解锁某个线程。

#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获取线程的计算结果

​ 通过使用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();
	}
}

3.4 promise主线程如何将数据发送数据到其他线程

​ 通过使用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");
}

​ 但这里也有一个问题需要思考,如果需要发送数据到多个线程,是不是需要一个个的创建上面的代码呢。这里就引出了多线程之间共享状态这个解决方法。

3.5 future.share()多线程之间共享状态

​ 通过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");
}

3.6 线程packaged_task

packaged_taskpromise非常相似,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
	}
}

3.7 时间约束

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

4. Windows多线程

​ 使用WindowsAPI进行多线程的编写,需要包含头文件

#include<windows.h>

4.1 Windows创建线程

​ 使用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
);
*/

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

传入的参数为:

/*

WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,      和线程安全有关一般为null
    _In_ BOOL bInitialOwner,                               有没有该锁的控制权
    _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);
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK