C++11的多线程并发编程(四)
—互斥量和死锁
- 互斥量 mutex
互斥量是一个类对象,作用是要是对要读写的数据进行加锁,通过调用lock()函数表示目前只有这个进程可以进行共享数据的操作,别的进程只有等待,当这个进程调用unlock函数的时候,表示解锁。
针对上一次案例,进行加锁和解锁操作,,lock和unlock是成双出现的,不可能你占用了一个锁,执行结束还占着,这样其他进程就无法获得锁。
#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
void inCommand()
{
for(int i = 0; i < 1000; i++)
{
mylock.lock();
ticket.push_back(i);
cout << "a command is comming" << endl;
mylock.unlock();
}
}
void outCommand()
{
for(int j = 0; j < 1000; j++)
{
mylock.lock();
if(!ticket.empty())
{
int command = ticket.front();
ticket.pop_front();
cout << "command is :" << command << endl;
mylock.unlock();
}
else
{
cout << "there is no people" << endl;
mylock.unlock();
}
}
}
private:
list<int> ticket;
mutex mylock;
};
int main(int argc, char *argv[])
{
A a;
thread thread1(&A::inCommand, &a);
thread thread2(&A::outCommand, &a);
thread1.join();
thread2.join();
cout << "the main thread is end;" << endl;
return 0;
}
这样就可以稳定运行起来了。
- lock_guard
上面的例子,会发现当程序运行中出现了一个问题,然后排查半天是因为,有个线程加锁,但是忘记写unlock()函数,因此c++提供了一个类模板lock_guard该功能就类似于智能指针解决忘记清除内存的问题一样,lock_guard提供构造函数,以及析构函数,构造函数就是加锁,析构函数就是解锁。,当创建的类对象超过了所在的作用域就会进行析构函数调用。
因此进行修改:
#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
void inCommand()
{
for(int i = 0; i < 1000; i++)
{
lock_guard<mutex> myguard(mylock);
// mylock.lock();
ticket.push_back(i);
cout << "a command is comming" << endl;
// mylock.unlock();
}
}
void outCommand()
{
for(int j = 0; j < 1000; j++)
{
lock_guard<mutex> myguard(mylock);
// mylock.lock();
if(!ticket.empty())
{
int command = ticket.front();
ticket.pop_front();
cout << "command is :" << command << endl;
// mylock.unlock();
}
else
{
cout << "there is no people" << endl;
// mylock.unlock();
}
}
}
private:
list<int> ticket;
mutex mylock;
};
int main(int argc, char *argv[])
{
A a;
thread thread1(&A::inCommand, &a);
thread thread2(&A::outCommand, &a);
thread1.join();
thread2.join();
cout << "the main thread is end;" << endl;
return 0;
}
可以观察到,运行结果与上一个例子相同,没有异常。
- 死锁
死锁出现的条件是两个线程都要运行,但两个线程都在等对方解锁,也就是,线程1加锁A,申请加锁B,而线程2加锁B,申请加锁A,这样线程12都没法继续执行下去。
那么在代码中就会看见这样情况:
mutex mymutex1;
mutex mymutex2;
thread1:
mymutex1.lock();
mymutex2.lock();
.....
mymutex1.unlock();
mymutex2.unlock();
thread2:
mymutex2.lock();
mymutex1.lock();
....
mymutex2.unlock();
mymutex1.unlock();
两个线程都执行完第一句,一个等着另一个,互相等着的,这样就是死锁。
- 解决办法
lock()与adopt_lock的作用,lock()函数参数就是mutex,有多少个锁就多少个参数,它表示,要么一起申请了,要么一起不要了,这样就不会造成一个死锁的状态,然后,在不需要的时候对每一个锁进行unlock()。还有一种办法就是,adopt_lock,是一个参数放在lock_guard中,
他表示lock_guard不执行构造函数,直接生命周期结束后直接析构函数。
看例子就明白了:
#include <iostream>
#include <thread>
#include <list>
#include <mutex>
using namespace std;
class A
{
public:
void inCommand()
{
for(int i = 0; i < 1000; i++)
{
lock(mylock1,mylock2);
lock_guard<mutex> myguard1(mylock1,adopt_lock);
lock_guard<mutex> myguard2(mylock2,adopt_lock);
// mylock.lock();
ticket.push_back(i);
cout << "a command is comming" << endl;
// mylock.unlock();
}
}
void outCommand()
{
for(int j = 0; j < 1000; j++)
{
lock(mylock1,mylock2);
lock_guard<mutex> myguard1(mylock1,adopt_lock);
lock_guard<mutex> myguard2(mylock2,adopt_lock);
// mylock.lock();
if(!ticket.empty())
{
int command = ticket.front();
ticket.pop_front();
cout << "command is :" << command << endl;
// mylock.unlock();
}
else
{
cout << "there is no people" << endl;
// mylock.unlock();
}
}
}
private:
list<int> ticket;
mutex mylock1;
mutex mylock2;
};
int main(int argc, char *argv[])
{
A a;
thread thread1(&A::inCommand, &a);
thread thread2(&A::outCommand, &a);
thread1.join();
thread2.join();
cout << "the main thread is end;" << endl;
return 0;
}
结果还是稳定执行,无异常。
疫情依旧很严重,小区都已经封了,望早日结束早日开学。