C++多线程编程超详解

2021-09-30,

目录
  • C++多线程
  • 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挂起和唤醒线程
  • 总结

    C++多线程

    1. 概念

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

    2. 常用API

    ​ 头文件#include<thread>

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

    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>

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

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

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

    总结

    本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注北冥有鱼的更多内容!

    《C++多线程编程超详解.doc》

    下载本文的Word格式文档,以方便收藏与打印。