一、lock_guard<mutex> lock()
多线程访问同一资源时,为了保证数据的一致性,必要时需要加锁mutex。
1. 直接操作mutex的lock/unlock函数
#include <iostream>
#include <boost>
std::mutex m_buf;
void Counter(){
m_buf.lock();
int i=++count;// 只有当一个线程输出完毕了,另一个线程才能输出
m_buf.unlock();
}
int main(){
boost::thread_group threads;// 创建进程
for(int i=0;i<4;i++)
threads.create_thread(&Counter);
threads.join_all();
return 0;
}
2. 使用 lock_guard/unique_lock 自动加锁、解锁。类似智能指针
与上面相比只需要加锁,后面无须解锁unlock().
unique_lock<mutex> lck(m);就是创建了一个unique_lock对象lck,并将其与互斥量m绑定,同时对其上锁。
#include <iostream>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
boost::mutex mutex;
int count = 0;
void Counter() {
// lock_guard 在构造函数里加锁,在析构函数里解锁。
boost::lock_guard<boost::mutex> lock(mutex);
int i = ++count;
std::cout << "count == " << i << std::endl;
}
int main() {
boost::thread_group threads;
for (int i = 0; i < 4; ++i) {
threads.create_thread(&Counter);
}
threads.join_all();
return 0;
}
unique_lock和lock_guard区别:
- unique_lock不需要始终拥有关联的mutex,而lock_guard始终拥有mutex。这意味着unique_lock需要利用owns_lock()判断是否拥有mutex。
- unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁
- unique_lock还接受第二个参数来进行构造。 unique_lock<mutex> lck(m,perd):仅仅是将lck与m绑定,不会自动进行lock()和unlock();后面需要自己unlock()。
另外,如果要结合使用条件变量condition_variable,应该使用unique_lock。下面进行详细解读。
二、条件变量condition_variable
estimator_node.cpp 中 process()函数最开始有个线程同步操作。
std::condition_variable con; //条件变量
// 在提取measurements时互斥锁m_buf会锁住,此时无法接收数据;等待measurements上面两个接收数据完成就会被唤醒
std::unique_lock<std::mutex> lk(m_buf);
con.wait(lk, [&]{return (measurements = getMeasurements()).size() != 0;});
lk.unlock();
调用了wait函数,条件变量的对象con和互斥锁lk绑定,之后判断第二个参数。
wait调用后,会先释放锁 lk->unlock() ,之后进入等待状态;当其它进程调用通知激活后,会再次加锁
线程同步指线程间需要按照预定的先后顺序进行的行为
- 互斥量(std::mutex)中,互斥锁是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。一个线程在往队列添加数据的时候,另一个线程不能取。unique_lock<mutex>
- 多线程间的状态同步通过条件变量。位于头文件condition_variable下
引出条件变量
using namespace std;
class MsgList //模拟消息的写入和读取
{
public:
void MsgWrite()// 写线程
{
for (int i = 0; i < 10000; i++) //写入1000条消息,用i来模拟消息
{
lock_guard<mutex> guard(m);// 自动加锁
msgQue.push_back(i); // 加到消息队列
cout << "Write Message : " << i << endl;
}
}
void MsgRead() // 读线程
{
while (true)
{
if (!msgQue.empty())//每次都要判断消息列表中是否有消息,如果有的话就将其读出
{
lock_guard<mutex> guard(m);
cout << "Read Message : " << msgQue.front() << endl; //读取最先来临的消息
msgQue.pop_front(); //将已读消息删除
}
else
{
lock_guard<mutex> guard(m); //这里加锁是为了保证cout输出完整
cout << "There is no Message !" << endl;
}
}
}
private:
deque<int> msgQue; //消息队列
mutex m; //互斥锁 m
};
int main()
{
MsgList myMsg;
thread ReadThread(&MsgList::MsgRead, &myMsg); //写线程
thread WriteThread(&MsgList::MsgWrite, &myMsg); //读线程
ReadThread.join();
WriteThread.join();
return 0;
}
问题: 通过互斥锁实现了msgQue的读和写,但是读线程函数是个死循环,程序会一直循环判断消息队列是否有消息,这样就造成了CPU不必要的开销。因为程序不知道到底消息队列中有没有消息,就一直需要去判断,如果能有一个好的办法,当消息队列中有消息的时候读线程再去执行,如果队列中没有消息的话就让读线程一直阻塞住。该如何实现呢?这就是条件变量condition_variable的作用了。
class MsgList
{
public:
void MsgWrite()// 写数据
{
for (int i = 0; i < 10000; i++)
{
unique_lock<mutex> lck(m);
msgQue.push_back(i);
cout << "Write Message : " << i << endl;
mycond.notify_one(); // !!!唤醒wait堵塞
}
}
void MsgRead() // 读数据
{
while (true)
{
unique_lock<mutex> lck(m);
// 如果队列不为空就继续向下执行。
mycond.wait(lck, [this]() { //调用wait函数,先解锁lck,然后判断lambda的返回值
return !msgQue.empty();
});
int ReadData = msgQue.front(); //执行到这里,说明msgQue非空,就可以读取数据了
msgQue.pop_front();
cout << "Read Message : " << ReadData << endl;
}
}
private:
deque<int> msgQue;
mutex m;
condition_variable mycond; // 多了条件变量
};
int main()
{
MsgList myMsg;
thread ReadThread(&MsgList::MsgRead, &myMsg); //写线程
thread WriteThread(&MsgList::MsgWrite, &myMsg); //读线程
ReadThread.join();
WriteThread.join();
return 0;
}
条件变量往往需要绑定一个unique_lock(也是互斥锁的一种实现),当在线程中通过条件变量调用wait函数时,该线程就会被阻塞住,直到在另一个线程中调用notify函数来唤醒这个线程。
条件变量mycode在wait函数中绑定了lck,判断第二个参数,如果返回false(队列为空)该线程被堵塞。
condition_variable
条件变量提供了两类操作:wait和notify,这两类操作构成了多线程同步。
#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
std::mutex g_mutex; // 用到的全局锁
std::condition_variable g_cond; // 用到的条件变量
int g_i = 0;
bool g_running = true;
void ThreadFunc(int n) { // 线程执行函数
for (int i = 0; i < n; ++i) {
{
std::lock_guard<std::mutex> lock(g_mutex); // 加锁,离开{}作用域后锁释放
++g_i;
std::cout << "plus g_i by func thread " << std::this_thread::get_id() << std::endl;
}
}
std::unique_lock<std::mutex> lock(g_mutex); // 加锁
while (g_running) {
std::cout << "wait for exit" << std::endl;
g_cond.wait(lock); // wait调用后,会先释放锁,之后进入等待状态;当其它进程调用通知激活后,会再次加锁
}
// 这一步要到notify()才会运行,线程唤醒
std::cout << "func thread exit" << std::endl;
}
int main() {
int n = 100;
std::thread t1(ThreadFunc, n); // 创建t1线程(func thread),t1会执行`ThreadFunc`中的指令,相当于运行到wait,剩下的要到notify()才能运行
for (int i = 0; i < n; ++i) {
{
std::lock_guard<std::mutex> lock(g_mutex);
++g_i;
std::cout << "plus g_i by main thread " << std::this_thread::get_id() << std::endl;
}
}
{
std::lock_guard<std::mutex> lock(g_mutex);
g_running = false;
g_cond.notify_one(); // 通知其它线程
}
t1.join(); // 等待线程t1结束
std::cout << "g_i = " << g_i << std::endl;
}
结果:
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by func thread 139921006847744
plus g_i by func thread 139921006847744
plus g_i by func thread 139921006847744
plus g_i by func thread 139921006847744
plus g_i by func thread 139921006847744
wait for exit // func thread等待main thread发来的退出信号
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
plus g_i by main thread 139921025066816
func thread exit
g_i = 200 // 锁机制保证了g_i的正确
两个线程交替执行,需要有先后顺序时,就可以通过条件变量这种机制来做到;
参考:
https://www.cnblogs.com/dengchj/p/9198121.html