C++多线程编程超详解
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_task
和promise
非常相似,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); }
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!
下一篇: 剪映app怎么使用创作脚本做视频特效?