C++多线程--互斥锁

本文介绍了在C++多线程环境中,如何使用互斥锁(mutex)确保线程安全。通过示例展示了不加锁时输出混乱的问题,然后详细解释了lock_guard和unique_lock两种锁管理器的使用方法及区别,强调了它们在防止资源死锁和提高效率方面的优势。建议在实际编程中使用lock_guard以避免手动解锁可能导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我们系统中或项目中,存在同时运行多个任务需要使用同一种资源的情况。比如说,同一个文件,线程1对其进行写操作,而线程2同时对这个文件进行写操作,如果线程1还没写完,此时线程2开始了,那么最终的结果显然会混乱。若操作对象为容器等,甚至程序会崩溃。

为了防止上述情况,保护共享资源,在线程里需要加锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。
下面我们用thread库创建两个线程,简单探索下加锁和不加锁的直观现象:
一. 不加锁的现象

#include<iostream>
#include<string>
#include<thread>
using namespace std;
void f() {
	cout << "线程id:" << this_thread::get_id() << endl;
}
int main()
{
	thread tTest1(f);
	thread tTest2(f);
	tTest1.join();
	tTest2.join();
	system("pause");
	return 0;
}

3次调用结果
上图是3次运行的结果,第一幅图结果使我们理想中的结果,实际结果却不一定一致。这是因为两个函数线程都在执行,而输出屏幕却只有一个,因此信息就会发生混乱。这样的线程是不具备安全性的,要想使其具有安全性,我们就可以使用锁来进行编程。
二. 加锁的现象
线程安全:当多个线程访问某一个类(对象或方法)时,对象对应的公共数据区始终都能正确输出,那么这个类(对象或方法)就是线程安全的。
1. 使用互斥锁(mutex):
注意必须手动调用unlock(),否则资源不释放

#include<iostream>
#include<string>
#include<thread>
#include<mutex>
using namespace std;
mutex m_lock;
void f() {
	m_lock.lock();
	cout << "线程id:" << this_thread::get_id() << endl;
	m_lock.unlock();
}
int main()
{
	thread tTest1(f);
	thread tTest2(f);
	tTest1.join();
	tTest2.join();
	system("pause");
	return 0;
}

正常运行下,无论多少次,输入都是我们理想得到的。在C++中,通过构造std::mutex的实例创建互斥元,调用成员函数lock()来锁定它,调用unlock()来解锁,这能满足我们的需求。不过一般不推荐这种做法,lock()和unlock()是成对出现的,需要手动解锁,若在手动释放锁前程序异常,没有调用unlock(),这个资源会一直被锁着,没发释放,会导致其他异常。

标准C++库提供了std::lock_guard类模板,实现了互斥元的RAII惯用语法。std::mutex和std::lock _ guard。都声明在< mutex >头文件中。

2. 使用std::lock _ guard
注意析构时自动释放,不能手动调用unlock(),否则析构时再次调用,会报异常。
在作用域内创建lock_guard对象时,会尝试获得锁(mutex::lock()),没有获得就像其他锁一样阻塞在原地。在lock_guard的析构函数内会释放锁(mutex::unlock()),不需要我们手动释放。

#include<iostream>
#include<string>
#include<thread>
#include<mutex>
using namespace std;
mutex m_lock;
void f() {
	lock_guard<mutex> lg(m_lock);
	cout << "线程id:" << this_thread::get_id() << endl;
}
int main()
{
	thread tTest1(f);
	thread tTest2(f);
	tTest1.join();
	tTest2.join();
	system("pause");
	return 0;
}

实际编程中建议使用lock_guard,防止在获得锁后程序运行出现异常退出而导致锁死。
注意:使用lock_guard时不要手动unlock(),否则lock_guard的析构函数中解锁时就会报异常。

3. 使用unique_lock
注意若没有手动调用unlock(),析构时自动释放;若手动调用unlock(),调用时释放。
unique_lock也是类模板,和lock_guard差不多,也是构造函数中加锁,析构函数解锁,符合RAII原则。但unique_lock解锁能力更灵活,给我们提供了更好的优化代码能力。
lock_guard在创建获得锁之后必须要等到析构时才会释放锁,这样就造成其他线程在执行不涉及临界资源的代码时浪费了时间,大大降低效率。
unique_lock在创建获得锁之后可等到析构时才会释放锁,也可以手动释放锁,这样可以在执行不涉及临界资源的代码是解锁,提高效率。

#include<iostream>
#include<string>
#include<thread>
#include<mutex>
using namespace std;
mutex m_lock;
void f() {
	unique_lock<mutex> lg(m_lock);
	cout << "线程id:" << this_thread::get_id() << endl;
}
void ff() {
	unique_lock<mutex> lg(m_lock);
	cout << "ff() 加锁." << endl;
	lg.unlock(); 
	cout << "ff() 已经解锁了." << endl;
}
int main()
{
	thread tTest1(f);
	thread tTest2(f);
	thread tTest3(ff);
	thread tTest4(ff);
	tTest1.join();
	tTest2.join();
	tTest3.join();
	tTest4.join();
	system("pause");
	return 0;
}

在这里插入图片描述
注意:unique_lock 赋值给另一个unique_lock后,原先的锁就会被释放,和unique_ptr有点像。

参考:
C++多线程
C++11线程中的几种锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qzy0621

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值