mutex,也就是最简单的互斥锁。使用lock(),unlock()函数来进行加锁解锁操作。
还记得在C++11多线程(一)中我提到的cout的不安全性吗?
由于多个线程同时在使用cout,因此输出流中的字符顺序就不像我们所期望的那样,于是,我们引入了锁这个概念。
这里补充一个概念:线程安全
当多个线程访问某一个类(对象或方法)时,对象对应的公共数据区始终都能表现正确,那么这个类(对象或方法)就是线程安全的。
#include
mutex Lock;
Lock.lock();
Lock.unLock();
//lock(),unlock()是成对出现的,就像new和delete一样。注意不要连续lock和unlock。
//Lock首字母大写,因为lock是一个函数(这里用lock只是形象一点),实际编写过程中还是用其他变量名
//当然还有try_lock(),查看补充部分
代码如下:
#include
#include
#include //引入头文件
using namespace std;
mutex m_lock;
void f() {
m_lock.lock();
cout << "正在执行" << this_thread::get_id()<<"A"<<"B"<<"C" << endl;
//"A"<<"B"<<"C" 用于多次输出表示线程安全
m_lock.unlock();
}
int main() {
thread t1(f);
thread t2(f);
t1.join();
t2.join();
return 0;
}
执行结果:
在t1,t2的运行过程中,当运行到第9行时,m_lock便会被其中一个线程锁定,而另一个线程就会尝试获得这把锁,
若没有获得,便会阻塞在原地直到获得锁。
换通俗的语法,你也可以理解为,lock代表尝试获得锁,成功获得继续执行,失败则阻塞直到获得锁。而unlock则代表释放锁的所有权。
这样,我们就用m_lock来保证了这两个线程的输出完整性。
但实际情况中,将一个mutex设置为全局变量不免有些浪费和不安全,因此我们最好使用一个类来处理锁和线程。
下面这段代码,我们尝试封装一个队列。利用Push()和Pop()函数来进行操作。
#include
#include
#include
#include
#include //rand()产生随机值
using namespace std;
class Test
{
public:
Test();
void Push(int i);
void Pop();
private:
queue<int> m_que;
mutex m_lock;
};
Test::Test() {
m_que = queue<int>();
}
void Test::Push(int i = 1)
{
m_lock.lock(); //写方法上锁
m_que.push(i);
cout << i << "被Push了" << endl;
m_lock.unlock(); //写方法解锁
}
void Test::Pop()
{
m_lock.lock(); //写方法上锁
int i;
if (!m_que.empty()) {
//这里是必要的 queue不为空的话pop会报异常
i = m_que.back();
m_que.pop();
cout << i << "被Pop了" << endl;
m_lock.unlock();//写方法解锁
return;
}
cout << "无数据" << endl;
m_lock.unlock(); //写方法解锁
//这里要注意考虑无数据情况下的解锁问题
}
int main() {
Test T_Class;
while (1) {
thread t1(&Test::Push, &T_Class, rand() % 10);//读线程
thread t2(&Test::Push, &T_Class, rand() % 10);
//可以思考一下T_Class为什么用&以及第三个参数的意义
//不明白的可以查看C++多线程(一)
thread t3(&Test::Push, &T_Class, rand() % 10);
thread t4(&Test::Pop, &T_Class); //写线程
t1.join();
t2.join();
t3.join();
t4.join();
}
return 0;
}
//可以自己改变一下读线程和写线程的数量观察运行状态。
像这样用来管理读写功能的锁可以使用 C++14的 shared_mutex,也就是读写锁。读写锁的效率会更高,我们只探讨了C++11,有兴趣的可以查阅相关资料
timed_mutex 和 mutex相似,也可以使用lock() / unlock()来获得 / 释放锁。
传入一个时间作为参数,等待一段时间,这段时间内如果取得了锁,返回true,没有拿到返回false;
我使用简洁的表达方式,具体参数意义可参考官方手册
timed_mutex T_lock;
if(T_lock.try_lock_for(std::chrono::milliseconds(100))){
//如果100ms内获得了锁就返回true
//chrono::milliseconds chrono是C++11下的一个时间库。感兴趣自行了解
dosomething();//执行动作
T_lock.unlock();//记得解锁,try_lock成功会上锁,补充有简单讲解。
}else{
dotherthing();
}
传入一个未来的时间点,在时间点前获得了锁就返回true,否则返回false;
timed_mutex T_lock;
if (T_lock.try_lock_until(chrono::steady_clock::now() + std::chrono::milliseconds timeout(100))) {
dosomething();//执行动作
T_lock.unlock();//记得解锁
}else {
dotherthing();
}
剩下两个锁因为我用的不多,就不多做阐述,给出
递归的独占互斥量,允许同一个线程,同一个互斥量,多次被lock,用法和非递归的一样 跟windows的临界区是一样的,但是调用次数是有上限的,效率也低一些
https://blog.csdn.net/qq_40666620/article/details/102702176
带超时的,递归的,独占互斥量,允许同一个线程,同一个互斥量,多次被lock,用法和非递归的一样
https://blog.csdn.net/qq_40666620/article/details/102702176
bool mutex::try_lock()
bool recursive_mutex::try_lock()
bool timed_mutex::try_lock()
三个成员方法尝试获得锁,成功返回true,失败返回false
注意:如果try_lock()返回了true,那调用此方法的mutex变量已经被上锁了,需要我们进行unlock。
感谢您的观看,要是出现没有讲解清楚或者错误的地方,欢迎您随时指出