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

本文详细介绍了C++11中的多线程概念,重点讲解了互斥量mutex的lock_guard和unique_lock的使用,以及条件变量condition_variable在线程同步中的应用。通过lock_guard和unique_lock的对比,展示了它们在加锁解锁上的不同策略。同时,阐述了条件变量如何解决读写线程间的同步问题,减少不必要的CPU开销。

目录

一、lock_guard  lock()

1. 直接操作mutex的lock/unlock函数

2. 使用 lock_guard/unique_lock 自动加锁、解锁。类似智能指针

3、unique_lock和lock_guard区别:

二、条件变量condition_variable

1、引出条件变量

2、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;
}

3、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

1、引出条件变量

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(队列为空)该线程被堵塞。

 

2、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

C++中,`std::mutex`和`std::condition_variable`是用于多线程编程的重要工具,它们在代码中的声明使用有各自的用途、场景和注意事项。 ### 用途 - **std::mutex**:`std::mutex`是一种互斥,主要用于保护共享资源,防止多个线程同时访问这些资源而导致数据竞争和不一致的问题。当一个线程获得了`std::mutex`的,其他线程试图获取该时会被阻塞,直到持有的线程释放。例如,在需要对共享数据进行读写操作时,使用`std::mutex`可以确保同一时间只有一个线程能够进行操作,保证数据的完整性 [^1][^3][^4]。 - **std::condition_variable**:`std::condition_variable`是一种条件变量,用于线程间的同步。它允许一个线程等待某个条件成立,而另一个线程可以通知该条件已发生变化。这样可以避免线程不断地轮询检查条件是否满足,提高程序的效率。例如,在生产者 - 消费者模型中,消费者线程可以等待队列中有元素(条件),而生产者线程在添加元素到队列后通知消费者线程 [^1][^2][^3]。 ### 使用场景 - **std::mutex**:适用于多个线程访问共享资源的场景,如多线程对同一个文件进行读写、多个线程修改同一个数据结构等。在这些场景中,使用`std::mutex`可以确保线程安全。例如,在服务器端开发中,多个线程可能会同时访问某个全局变,这时就需要使用`std::mutex`来保护该变 [^1]。 - **std::condition_variable**:常用于生产者 - 消费者模型、读写实现、线程池等场景。在生产者 - 消费者模型中,生产者线程向队列中添加元素,消费者线程从队列中取出元素。当队列为空时,消费者线程可以等待`std::condition_variable`,直到生产者线程添加元素并通知它 [^1]。 ### 注意事项 - **std::mutex**: - 避免死:死是指两个或多个线程相互等待对方释放的情况。为了避免死,需要确保线程以相同的顺序获取,或者使用`std::lock`等工具来一次性获取多个- 的粒度:的粒度不宜过大或过小。过大的粒度会导致并发性能下降,因为多个线程需要等待同一个;过小的粒度会增加的开销,也可能导致死- 及时释放:在完成对共享资源的操作后,应及时释放,以允许其他线程访问该资源。可以使用`std::lock_guard`或`std::unique_lock`来自动管理的生命周期 [^3][^5]。 - **std::condition_variable**: -互斥配合使用:`std::condition_variable`必须与`std::mutex`一起使用,因为在等待条件变量时需要持有,以确保条件的检查和修改是原子操作。 - 虚假唤醒:`std::condition_variable`可能会出现虚假唤醒的情况,即线程在没有收到通知的情况下被唤醒。因此,在等待条件变量时,应该使用循环检查条件是否真正满足。 以下是一个简单的代码示例,展示了`std::mutex`和`std::condition_variable`的使用: ```cpp #include <iostream> #include <queue> #include <thread> #include <mutex> #include <condition_variable> std::queue<int> queue; std::mutex mtx; std::condition_variable cv; // 生产者线程函数 void producer() { for (int i = 0; i < 5; ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟生产过程 { std::lock_guard<std::mutex> lock(mtx); queue.push(i); std::cout << "Produced: " << i << std::endl; } cv.notify_one(); // 通知消费者线程 } } // 消费者线程函数 void consumer() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [] { return !queue.empty(); }); // 等待队列非空 int item = queue.front(); queue.pop(); lock.unlock(); // 释放 std::cout << "Consumed: " << item << std::endl; } } int main() { std::thread producerThread(producer); std::thread consumerThread(consumer); producerThread.join(); // 由于消费者线程是无限循环,这里不调用 join,可以手动终止程序 // consumerThread.join(); return 0; } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值