互斥量的基本概念
- 临界资源:每次只允许一个线程进行访问(读/写)的资源
- 线程间的互斥(竞争):多个线程在同一时刻都需要访问临界资源
mutex 类(互斥量)是一把线程锁,保证线程间的互斥
- 利用线程锁能够保证临界资源的安全
互斥量的用法
lock
mutex.lock()
- 当锁空闲时,获取并继续执行
- 当锁被获取时,阻塞并等待锁释放
mutex.unlock()
- 释放锁(同一把锁的获取和释放必须在同一线程中成对出现)
如果 mutex 在调用 unlock() 时处于空闲状态,那么程序的行为是未定义的
lock_guard
- 在 lock_guard 对象构造时,传入的 mutex 对象(即它管理的 mutex 对象)会被当前线程锁住。
- 在 lock_guard 对象被析构时,它所管理的 mutex 对象会自动解锁,不再需要手动调用 lock 和 unlock 对mutex 进行上锁和解锁操作;
- lock_guard 对象并不负责 mutex 对象的生命周期,只是简化了上锁和解锁操作;
- 这种采用“资源分配时初始化”(RAII)方法来加锁、解锁,避免了在临界区中因为抛出异常或return等操作导致没有解锁就退出的问题。这极大的简化了编写mutex相关的异常处理代码。
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
using namespace std;
class Handle {
public:
void inMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex.lock(); // 注意这里 !!!
cout << "inMsgRevQueue() : " << i << endl;
m_queue.push(i);
m_mutex.unlock(); // 注意这里 !!!
}
}
void outMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
lock_guard<mutex> lck(m_mutex); // 注意这里 !!!
if (!m_queue.empty())
{
cout << "outMsgRevQueue() : " << m_queue.front() << endl;
m_queue.pop();
}
}
}
private:
queue<int> m_queue;
mutex m_mutex;
};
int main()
{
cout << "main begin" << endl;
Handle handler;
thread th1(&Handle::inMsgRevQueue, std::ref(handler));
thread th2(&Handle::outMsgRevQueue, std::ref(handler));
th1.join();
th2.join();
cout << "main end" << endl;
return 0;
}
输出:
......
outMsgRevQueue() : 98267
outMsgRevQueue() : 98268
outMsgRevQueue() : 98269
outMsgRevQueue() : 98270
main end
死锁
概念
- 线程间互相等待临界资源而造成彼此无法继续向下运行
发生死锁的条件
- 系统存在多个临界资源且临界资源不可抢占
- 线程需要多个临界资源才能继续运行
死锁的演示
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
using namespace std;
class Handle {
public:
void inMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
cout << "inMsgRevQueue() : " << i << endl;
m_queue.push(i);
m_mutex2.unlock();
m_mutex1.unlock();
}
}
void outMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex2.lock();
m_mutex1.lock();
if (!m_queue.empty())
{
cout << "outMsgRevQueue() : " << m_queue.front() << endl;
m_queue.pop();
}
m_mutex1.unlock();
m_mutex2.unlock();
}
}
private:
queue<int> m_queue;
mutex m_mutex1;
mutex m_mutex2;
};
int main()
{
cout << "main begin" << endl;
Handle handler;
thread th1(&Handle::inMsgRevQueue, std::ref(handler));
thread th2(&Handle::outMsgRevQueue, std::ref(handler));
th1.join();
th2.join();
cout << "main end" << endl;
return 0;
}
输出:
inMsgRevQueue() : 63
inMsgRevQueue() : 64
inMsgRevQueue() : 65
inMsgRevQueue() : 66
inMsgRevQueue() : 67
不再输出 ...
死锁的避免
死锁的一般解决方案
- 对所有的临界资源都分配一个唯一的编号 (n1, n2, n3)
- 对应的线程锁也分配同样的序号 (m1, m2, m3)
- 系统中的每个线程按照严格递增(递减)的次序请求资源
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
using namespace std;
class Handle {
public:
void inMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
cout << "inMsgRevQueue() : " << i << endl;
m_queue.push(i);
m_mutex2.unlock();
m_mutex1.unlock();
}
}
void outMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
if (!m_queue.empty())
{
cout << "outMsgRevQueue() : " << m_queue.front() << endl;
m_queue.pop();
}
m_mutex2.unlock();
m_mutex1.unlock();
}
}
private:
queue<int> m_queue;
mutex m_mutex1;
mutex m_mutex2;
};
int main()
{
cout << "main begin" << endl;
Handle handler;
thread th1(&Handle::inMsgRevQueue, std::ref(handler));
thread th2(&Handle::outMsgRevQueue, std::ref(handler));
th1.join();
th2.join();
cout << "main end" << endl;
return 0;
}
输出:
inMsgRevQueue() : 99995
inMsgRevQueue() : 99996
inMsgRevQueue() : 99997
inMsgRevQueue() : 99998
inMsgRevQueue() : 99999
main end
std::lock 函数模板
lock(mutex1, mutex2, ...);
一次锁定两个或多个锁,用于处理多个互斥量
- 当全部锁空闲时,获取并继续执行
当全部锁未被获取时,阻塞并等待锁释放
- 当只获取其中一部分锁,另一部分锁被其它线程锁定时,则释放当前已获取的锁,继续等待并重新尝试获取全部锁,以此防止死锁
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
using namespace std;
class Handle {
public:
void inMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex1.lock();
m_mutex2.lock();
cout << "inMsgRevQueue() : " << i << endl;
m_queue.push(i);
m_mutex2.unlock();
m_mutex1.unlock();
}
}
void outMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
lock(m_mutex1, m_mutex2); // 注意这里 !!!
if (!m_queue.empty())
{
cout << "outMsgRevQueue() : " << m_queue.front() << endl;
m_queue.pop();
}
m_mutex2.unlock();
m_mutex1.unlock();
}
}
private:
queue<int> m_queue;
mutex m_mutex1;
mutex m_mutex2;
};
int main()
{
cout << "main begin" << endl;
Handle handler;
thread th1(&Handle::inMsgRevQueue, std::ref(handler));
thread th2(&Handle::outMsgRevQueue, std::ref(handler));
th1.join();
th2.join();
cout << "main end" << endl;
return 0;
}
输出:
outMsgRevQueue() : 99599
outMsgRevQueue() : 99600
outMsgRevQueue() : 99601
outMsgRevQueue() : 99602
main end
std::lock_guard 的 std::adopt_lock 参数
使用 adopt_lock 构造的 unique_lock 和 lock_guard 对象在构造时不会锁定mutex对象,而是假定它已被当前线程锁定。
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
using namespace std;
class Handle {
public:
void inMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex.lock();
cout << "inMsgRevQueue() : " << i << endl;
m_queue.push(i);
m_mutex.unlock();
}
}
void outMsgRevQueue()
{
for (int i=0; i<100000; ++i)
{
m_mutex.lock();
lock_guard<mutex> lck(m_mutex, adopt_lock); // 注意这里 !!!
if (!m_queue.empty())
{
cout << "outMsgRevQueue() : " << m_queue.front() << endl;
m_queue.pop();
}
}
}
private:
queue<int> m_queue;
mutex m_mutex;
};
int main()
{
cout << "main begin" << endl;
Handle handler;
thread th1(&Handle::inMsgRevQueue, std::ref(handler));
thread th2(&Handle::outMsgRevQueue, std::ref(handler));
th1.join();
th2.join();
cout << "main end" << endl;
return 0;
}
输出:
......
outMsgRevQueue() : 97515
outMsgRevQueue() : 97516
outMsgRevQueue() : 97517
outMsgRevQueue() : 97518
main end
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。