C++11多线程---互斥量mutex、锁lock、条件变量std::condition_variable

一、lock_guard<mutex>  lock()

多线程访问同一资源时,为了保证数据的一致性,必要时需要加锁mutex。

任一时刻,只能有一个线程访问该对象,加锁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区别:

  1. unique_lock不需要始终拥有关联的mutex,而lock_guard始终拥有mutex。这意味着unique_lock需要利用owns_lock()判断是否拥有mutex。
  2. unique_lock可以进行临时解锁和再上锁,如在构造对象之后使用lck.unlock()就可以进行解锁,lck.lock()进行上锁,而不必等到析构时自动解锁
  3. 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的作用了。

扫描二维码关注公众号,回复: 9856322 查看本文章
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

条件变量提供了两类操作:waitnotify,这两类操作构成了多线程同步。

#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

https://www.jianshu.com/p/c1dfa1d40f53 

https://www.jianshu.com/p/560283858587

发布了175 篇原创文章 · 获赞 84 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/try_again_later/article/details/104864189