c++线程二
2022/3/21 18:00:40
本文主要是介绍c++线程二,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
接上篇!
其实用mutex的lock()、unlock(), 当然更好用lock_guard(),这些基本能满足我们的应用需求,且容易理解。
一、unique_lock 相比lock_gaurd有一些更灵活的用法
主要体现在unique_lock的参数和成员函数上。如下一目了然,不再举例子。
unique_lock<mutex> uniqeLock(mu, std::adopt_lock);//同lock_guard, 指示不加锁,前提是mutex已经加锁 unique_lock<mutex> uniqeLock(mu, std::try_to_lock);//尝试加锁,用在获取不到锁时执行一些其他操作 unique_lock<mutex> uniqeLock(mu, std::defer_lock); //不加锁,且mutex尚未锁定,配合成员函数使用 uniqeLock.try_lock(); //同参数std::try_to_lock uniqeLock.owns_lock(); //返回bool,表明是否拥有锁 uniqeLock.release(); //返回mutex,解除unique_lock与mutex的绑定,unique_lock为空
其次,我们常用的用法是unique_lock的控制粒度很细。
unique_lock在析构时会自动判断是否需要解锁:
unique_lock<mutex> ulock(mu); //执行一些操作 ulock.unlock(); //我们可以随时unlock ulock.lock(); //执行另外的操作 //不用unlock,会自动判断
二、以上的lock_guard、unique_lock用法都是基于mutex,
除此之外还有std::recursive_mutex和std::timed_mutex
1、recursive_mutex:递归互斥锁,相对于mutex的互斥锁
mutex myMutex; void fun1() { myMutex.lock(); fun2(); //同样fun2中也需要锁,这种情况mutex显然不能胜任 //线程1操作.... myMutex.unlock(); } void fun2() { myMutex.lock(); //线程2操作.... myMutex.unlock(); } int main() { myMutex.lock(); fun1(); //调用fun1,但mutex只能lock一次 myMutex.unlock(); return 0; }
这种情况,只能用recursive_lock,能递归加锁,用于这种在同一线程嵌套调用加锁的情况。
即同一线程内lock或try_to_lock成功后开始占有锁,并可以多次lock,直到匹配到同样多的unlock后释放锁。早占有期间,其他线程lock会阻塞!
当然同mutex一样,最好是配合lock_guard和unique_lock使用。
2、std::timed_mutex 在一定时期内尝试获取锁,获取到锁就返回。
1、try_lock_for类似与unique_lock<mutx> uLock(mu, std::try_to_lock);
void fun2() { while (true) { if (tMutex.try_lock_for(chrono::seconds(1))) { cout << "fun2 开始执行" << endl; tMutex.unlock(); } else { cout << "fun2 没获取锁" << endl; } } }
2、try_lock_until()
tMutex.try_lock_until(chrono::steady_clock::now()+10s)
3、当然同mutex,超时锁也有递归版本 :recursive_timed_mutex
二、条件变量condition_variable
互斥锁是用来防止竞争问题,条件变量是解决“线程同步”问题。一个线程处理完后,通知另外的线程处理。
1、wait()、 notify_one()、notify_all()
对wait()的使用,详见代码的注释,因为只要通知一个线程,所以用notify_one()通知。
class A { public: void inQueueMsg() { for (int i = 0; i < 100000; i++) { unique_lock<mutex> uniqueLock(mu); cout << "线程id:" << this_thread::get_id() << " 插入数据" << endl; msgQueue.push(i); condv.notify_one(); } } void outQueueMsg() { for (int i = 0; i < 100000; i++) { unique_lock<mutex> uniqLock(mu); condv.wait(uniqLock, [this]() { //wait有两个重载 分别为1个参数和两个参数,都必须用unique_lock if (msgQueue.empty()) //wait会阻塞线程,直到收到notify信号,此时的阻塞会释放拿到的锁,并进入睡眠 return false; //收到notify信号,会再次尝试获取锁,获取后若没有第二个参数则走下去, return true; //若有第二个参数,则判断,为真走下去,为假则再次释放锁,进入睡眠 }); //走到此处说明非空,不用判断 int m = msgQueue.front(); msgQueue.pop(); cout << "outQueueMsg线程id:" << this_thread::get_id << " 读出数据:" << m << endl; } } private: queue<int> msgQueue; mutex mu; condition_variable condv; }; int main() { A ac; thread t1(&A::outQueueMsg, &ac); thread t2(&A::inQueueMsg, &ac); t1.join(); t2.join(); return 0; }
notify_one()只会唤醒一个wait的线程,此线程尝试去拿锁,拿不到则一直尝试拿;
nofity_all()唤醒所有wait的线程,所有线程都会去拿锁,但只有一个拿到,所以会“惊群”,其他拿不到锁的都会一直尝试拿锁!
另外,如果发送notify时,其他线程没在wait上阻塞,则此次notify不会产生任何作用(notify信号不会被储存,而是直接消失)。
三、原子操作
因为这种互斥的情况,c++标准增加原子操作,std::atomic<>类模板,但只能是针对内置普通变量,所以一般只用在计数上。
std::atomic<int> g_count = 0; void func() { for (int i = 0; i < 100000; i++) g_count++; //不需互斥,因为本身是原子操作,执行时cpu不会调度 } int main() { thread t1(func); thread t2(func); //多个线程对g_count进行操作 t1.join(); t2.join(); cout << "g_count=" << g_count << endl; return 0; }
这篇关于c++线程二的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享