C++多线程编程超详解

C++多线程编程超详解 1. 概念进程一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间一个进程可以有多个线程比如在Windows系统中一个运行的xx.exe就是一个进程。线程进程中的一个执行任务控制单元负责当前进程中程序的执行。一个进程至少有一个线程一个进程可以运行多个线程多个线程可共享数据。与进程不同的是同类的多个线程共享进程的堆和方法区资源但每个线程有自己的程序计数器、虚拟机栈和本地方法栈所以系统在产生一个线程或是在各个线程之间作切换工作时负担要比进程小得多也正因为如此线程也被称为轻量级进程。并发并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见比如在跑步的时候同时听音乐在看电脑显示器的同时敲击键盘等。同一时间段内可以交替处理多个操作强调同一时段内交替发生。并行同一时刻内同时处理多个操作强调同一时刻点同时发生。2. 常用API​ 头文件#includethread1.threadAPI描述注意thread.join()加入线程(会阻塞主线程模拟同步操作)thread.detach()加入线程(不会阻塞主线程模拟异步操作)thread.joinable()是否可加入线程返回boolthread.get_id()获取线程的IDthread.hardware_concurrency()获取硬件并发的数量thread.swap()交换线程thread.native_handle()获取原生handle为windows多线程中CreateThread的返回值使用这个handle从而可以实现线程的挂起唤醒测试代码123456789101112131415161718192021222324252627voidthreadFunc01() {cout thread join1 endl;this_thread::sleep_for(chrono::seconds(2));}voidthreadFunc02() {cout thread join2 endl;this_thread::sleep_for(chrono::seconds(2));}voidtest01() {// 创建线程std::threadthread1(threadFunc01);std::threadthread2(threadFunc02);//thread.join(); //join 会阻塞主线程 同步操作//thread.detach(); //detach 不会阻塞主线程 异步操作boolbJoinAble thread1.joinable();thread::id threadId thread1.get_id();//hardware_concurrency 硬件并发的数量intthreadNum thread1.hardware_concurrency();cout hardware_concurrency: threadNum endl;//应用 线程的预分配。for(inti 0; i thread1.hardware_concurrency(); i) {std::threadthreadRef(threadFunc01);threadRef.detach();}thread1.swap(thread2);thread1.join();}向线程里传递参数的方法1234567891011121314151617181920212223242526// 向线程里传递参数的方法#includestringvoidthreadFunc03(intnum,conststring str) {cout num num str str endl;}structFObject {voidRun(conststring str) {cout str endl;}};voidtest02() {// 通过函数绑定threadnewThread1(threadFunc03, 10,Unreal);newThread1.detach();// 通过lambda绑定inta 50;threadnewThread2([](intnum,conststring str) {cout a a num num str str endl;}, 1,Unreal);newThread2.detach();// 绑定对象FObject objectRef;threadnewThread3(FObject::Run, objectRef,Unreal);newThread3.detach();}2.互斥锁mutex​ 头文件#includemutexAPI描述注意mutex.lock()上锁mutex.unlock()解锁mutex.try_lock()判断可不可以加锁,返回bool可以用该方法建立非阻塞模式测试代码123456789101112131415161718#includemutexmutex lockRef;voidthreadFunc04(intnum,conststring str) {// 进入该线程锁住该线程其他线程想要进入该线程需要排队lockRef.lock();cout thread join4 endl;this_thread::sleep_for(chrono::seconds(2));// 解锁lockRef.unlock();}voidtest03() {std::threadthread1(threadFunc04, 10,Unreal);std::threadthread2(threadFunc04, 5,Unity);std::threadthread3(threadFunc04, 20,Cocos);thread1.detach();thread2.detach();thread3.detach();}使用类加锁的方式123456789101112131415161718192021222324252627#includemutexmutex lockRef;structFEvent {FEvent() {m.lock();}~FEvent(){m.unlock();}staticmutex m;};mutex FEvent::m;#define LOCK_SCOPE FEvent EventvoidthreadFunc04(intnum,conststring str) {LOCK_SCOPE;//加上锁,并且过了这个作用域自动解锁(析构)cout thread join4 endl;this_thread::sleep_for(chrono::seconds(2));}voidtest03() {std::threadthread1(threadFunc04, 10,Unreal);std::threadthread2(threadFunc04, 5,Unity);std::threadthread3(threadFunc04, 20,Cocos);thread1.detach();thread2.detach();thread3.detach();}try_lock()12345678voidthreadFunc04(intnum,conststring str) {boolbLock FEvent::m.try_lock();if(bLock) {LOCK_SCOPE;//加上锁,并且过了这个作用域自动解锁(析构)cout thread join4 endl;this_thread::sleep_for(chrono::seconds(2));}}​ 使用try_lock()可以进行判断能不能上锁不能上锁的话就不用执行上锁后的代码防止其他线程阻塞在该线程。lock_guardlock_guard是一种锁类作用和我们上面自定义的锁类FEvent相同创建的时候锁住目标线程释放的时候解锁。12// 声明方式lock_guardmutexref;源码1234567891011121314151617templateclass_Mutexclasslock_guard {// class with destructor that unlocks a mutexpublic:usingmutex_type _Mutex;explicitlock_guard(_Mutex _Mtx) : _MyMutex(_Mtx) {// construct and lock_MyMutex.lock();}lock_guard(_Mutex _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {// construct but dont lock}~lock_guard() noexcept {_MyMutex.unlock();}lock_guard(constlock_guard) delete;lock_guard operator(constlock_guard) delete;private:_Mutex _MyMutex;};unique_lock​ 作用和lock_guard相同唯一的不同之处lock_guard开放的API只有析构函数而unique_lock开放的API非常多即自由度比lock_guard高可以定义锁的行为。123456789101112voidtest05() {// defer_lock 关键字为延迟锁即创建该对象时不会锁住该线程什么时候锁需要自定义std::unique_lockmutexlockRef2(FEvent::m,defer_lock);std::unique_lockmutexlockRef2(FEvent::m,chrono::seconds(2));//锁两秒//....执行lockRef2.lock();lockRef2.unlock();boolbLock1 lockRef2.try_lock();//尝试上锁lockRef2.try_lock_for(chrono::seconds(2));//锁2smutex *lockRef3 lockRef2.release();//释放锁同时会返回被释放的这个锁的指针对象boolbLock2 lockRef2.owns_lock();//当前是否被锁住}应用123456789101112voidtest05() {//std::lock_guardmutexlockRef1(FEvent::m);// defer_lock 关键字为延迟锁std::unique_lockmutexlockRef2(FEvent::m,defer_lock);lockRef2.lock();lockRef2.mutex();boolbLock lockRef2.owns_lock();std::unique_lockmutexlockRef3;lockRef2.swap(lockRef3);std::unique_lockmutexlockRef4 move(lockRef3);lockRef4.unlock();}3. 挂起和唤醒​ 头文件#includewindows.h111111111111111111111111111111测试代码1234567891011121314151617#includewindows.hvoidthreadFunc05() {while(true){Sleep(10);cout threadFunc05 endl;}}voidtest04() {threadthread1(threadFunc05);// 挂起线程SuspendThread(thread1.native_handle());Sleep(2);// 唤醒线程ResumeThread(thread1.native_handle());}如何高效将主线程资源进行转移12345678910voidthreadFunc06(constchar* str) {cout str endl;}voidtest04() {// 如何高效转移线程资源// 使用std::movethreadthread2(threadFunc06, move(Unreal));// 使用move避免了拷贝threadthread3 move(thread2);thread3.detach();}3. 应用场景3.1 call_once执行一次的函数​ 通过使用该函数用来防止多线程的多次触发。123456789101112once_flag tag;voidcallonceTest() {call_once(tag, []() {cout Do once endl;});}voidtest06() {for(inti 0; i 10; i) {threadthread1(callonceTest);thread1.detach();}}3.2 condition_variable条件锁​ 使用需要包含头文件#includecondition_variable可以使用条件锁来达到同步的作用即当满足一定的条件后才解锁某个线程。1234567891011121314#includecondition_variablecondition_variable condition_lock;mutex mutexLock;voidconditionFuncTest() {unique_lockmutexlock(mutexLock);condition_lock.wait(lock);//锁住该线程cout Run endl;}voidtest12() {std::threadthreadRef(conditionFuncTest);threadRef.detach();Sleep(3000);//3s后再激活condition_lock.notify_one();}3.3 future获取线程的计算结果​ 通过使用future可以得到未来线程被调用的时候计算得返回值使用时需要包含头文件#includefuture。声明方式:12// async为创建该线程的方式为异步 funName 函数名 args为传入的函数参数std::futurestringnewFuture std::async(launch::async, funName,args...);应用1234567891011121314#includefuturestring getString(intnum) {returnUnreal;}voidtest08() {std::futurestringnewFuture std::async(launch::async, getString, 10);//std::futurestringnewFuture 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中获取。​ 测试代码1234567891011// promisestring promiseTest(futurestring future) {cout future.get() endl;returnUnreal;}voidtest09() {promisestring promiseRef;futurestringfuture1 promiseRef.get_future();futurestringfuture2 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()我们可以很方便的使多个线程之间共享状态。现在来看看没有使用该函数的话我们要共享状态的话需要这么写:1234567891011121314string promiseTest(futurestring future) {cout future.get() endl;returnUnreal;}voidtest09() {promisestring promiseRef;futurestringfuture1 promiseRef.get_future();futurestringfuture2 promiseRef.get_future();futurestringfuture3 promiseRef.get_future();futurestringfuture4 std::async(launch::async, promiseTest, std::ref(future1));//future 不支持值拷贝 需要传递引用futurestringfuture5 std::async(launch::async, promiseTest, std::ref(future2));//future 不支持值拷贝 需要传递引用futurestringfuture6 std::async(launch::async, promiseTest, std::ref(future3));//future 不支持值拷贝 需要传递引用promiseRef.set_value(Unreal is the best game engine in the world);}使用了future.share()函数后:1234567891011121314string promiseTest02(shared_futurestring future) {cout future.get() endl;returnUnreal;}voidtest09() {promisestring promiseRef;futurestringfuture1 promiseRef.get_future();// shared_futureshared_futurestring sharedFutrue1 future1.share();futurestringfuture2 std::async(launch::async, promiseTest02, sharedFutrue1);//shared_future 可以用拷贝传递futurestringfuture3 std::async(launch::async, promiseTest02, sharedFutrue1);futurestringfuture4 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_taskF是对promiseT std::functionF中T std::functionF这一可调对象(如函数、lambda表达式等)进行了包装简化了使用方法。并将这一可调对象的返回结果传递给关联的future对象。绑定Lambda123456789101112voidtest10() {//绑定lambdapackaged_taskint(int,int) task1([](inta,intb) -int{returna b;});task1(1, 4);this_thread::sleep_for(chrono::seconds(1));if(task1.valid()) {auto f1 task1.get_future();cout f1.get() endl;}}绑定普通函数12345678910111213intpackagedTest(inta,intb) {returna b;}voidtest10() {//绑定函数packaged_taskint(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进行函数绑定12345678910111213intpackagedTest(inta,intb) {returna b;}voidtest10() {// bindpackaged_taskint(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;//12}}3.7 时间约束1234567voidtest11() {//休眠2sthis_thread::sleep_for(chrono::seconds(2));// 休眠现在的时间加上2schrono::steady_clock::time_point timePos chrono::steady_clock::now() chrono::seconds(2);this_thread::sleep_until(timePos);}4. Windows多线程​ 使用WindowsAPI进行多线程的编写需要包含头文件1#includewindows.h4.1 Windows创建线程​ 使用CreateThread()创建线程123456789101112DWORDWINAPI funcThread(LPVOIDlpPram) {// DWORD 类型为unsigned long// LPVOID 类型为voidcout Unreal! endl;Sleep(1000);return0l;}voidwindowsThreadTest01() {HANDLEhandleRef CreateThread(nullptr,0, funcThread,nullptr,0,nullptr);Sleep(2000);CloseHandle(handleRef);//使用之后需要关闭handle}​ 其中传入的参数为1234567891011121314/*WINBASEAPI_Ret_maybenull_HANDLEWINAPICreateThread(_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互斥锁1234567891011121314151617// windows互斥锁HANDLEhMutex nullptr;DWORDWINAPI funcThread02(LPVOIDlpParam) {cout Unreal endl;WaitForSingleObject(hMutex, INFINITE);Sleep(5000);ReleaseMutex(hMutex);return0l;}voidwindowsThreadTest02() {hMutex CreateMutex(nullptr,false, LMutex);HANDLEhandleRef1 CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);HANDLEhandleRef2 CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);CloseHandle(handleRef1);CloseHandle(handleRef2);}传入的参数为1234567891011/*WINBASEAPI_Ret_maybenull_HANDLEWINAPICreateMutexW(_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, 和线程安全有关一般为null_In_ BOOL bInitialOwner, 有没有该锁的控制权_In_opt_ LPCWSTR lpName 锁名字);*/4.3 Windows挂起和唤醒线程​ 通过使用SuspendThread(HandleRef)和ResumeThread(HandleRef)来挂起和唤醒线程12345678910111213141516// windows 挂起唤醒DWORDWINAPI funcThread03(LPVOIDlpParam) {while(true) {Sleep(500);cout IsRunning endl;}return0l;}voidwindowsThreadTest03() {HANDLEhRef CreateThread(nullptr, 0, funcThread03, nullptr, 0, nullptr);SuspendThread(hRef);Sleep(2000);ResumeThread(hRef);CloseHandle(hRef);}总结本篇文章就到这里了希望能够给你带来帮助