互斥量的基本概念
- 临界资源:每次只容许一个线程进行拜访(读 / 写)的资源
- 线程间的互斥(竞争):多个线程在同一时刻都须要拜访临界资源
-
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