「C++ 多线程」std::lock
文章大图来源: pixiv_id=114384607
1 问题背景
通常情况下我们手动管理线程的加锁与解锁,会在代码的多个地方正确合理地编写 lock() 和 unlock() 语句。但是,万一我们忘记了加锁、解锁的操作,此时会导致出现互斥量没有及时释放而导致的 死锁 等问题,这会导致程序无法继续执行。而且,频繁地手动进行 lock() 和 unlock() 有时会显得代码 结构比较复杂。
2 std::lock_guard
2.1 基本概念及作用
C++ 11 中引入了 std::lock_guard,包含在
std::lock_guard 是一个类模板,基本的函数原型为 template
2.2 基本使用方法
在定义 std::lock_guard 对象的位置,std::lock_guard 对象对互斥量进行加锁 lock();在对象析构的位置(作用域结束,通常是 return 的位置),std::lock_guard 对象对互斥量进行解锁 unlock()。
假如我们我们有多个线程,并且定义了一个共享数据变量 count,以及一个 std::mutex 互斥量。在线程函数 foo() 中,在 同一作用域 中,对共享变量进行操作之前,可以定义一个 std::lock_guard 对象,此时会自动进行对互斥量加锁;对共享变量操作完,在 作用域结束 的位置,会自动进行解锁。
这样就可以代替 lock() 和 unlock() 的作用,确保多个线程互斥访问共享数据。
例如下面这种形式:
123456void foo(){ // 定义 lock_guard 对象,自动进行加锁 std::lock_guard
注意: 同一个线程中,std::lock_guard 和 lock()/unlock() 不能混用,如果用了 std::lock_guard,就禁止手动再调用 lock() 和 unlock(),否则会导致死锁或者资源没有正确释放等问题。
2.3 代码示例
12345678910111213141516171819202122232425262728#include
在上面的代码中,定义了两个线程 t1 和 t2,线程函数均为 foo(),传递的参数均为 100000,意为对共享数据变量 count 自加 100000 次。在循环每次迭代中,定义一个 std::lock_guard 对象,实现自动加锁和作用域结束后的自动解锁,确保对共享数据的互斥访问。
可能的运行结果如下:
在对数据操作的过程中,打印线程的 id 以及当前变量 count 的值。在输出过程中可以发现,两个线程来回切换进行,表明两个线程是并发(并行)执行的;打印的 count 值保持不断递增,表明我们的两个子线程互斥访问和修改共享数据变量。
最终输出的结果为 200000,我们的程序正确执行了两个线程 t1 和 t2 同时对 count 进行 100000 次自加修改的操作,对共享数据的访问与修改是互斥的。
3 adopt_lock 参数
std::adopt_lock 是一个标记类型,可以用于 std::lock_guard 的构造函数。当传递这个参数时,表示互斥量已经被锁住,此时对互斥量的锁的管理直接转移给 std::lock_guard,这样可以确保在合适的时候释放锁,不需要再调用 std::mutex::unlock() 去手动释放锁。
1234567891011121314151617181920212223#include
可能的运行结果如下:
与 std::unique_lock (「C++ 多线程」std::unique_lock 基本用法)不同,std::lock_guard 的构造函数只接受互斥量对象和可选的 std::adopt_lock 参数。它的设计目的是提供一种简单、高效的机制来确保互斥量在作用域结束时被正确释放。而其它比如 std::defer_lock 等参数 std::lock_guard 是不可以使用的。
4 简单应用实例
和之前 std::mutex 文章 「C++ 多线程」std::mutex,lock(), unlock() 中的问题相同:
一个线程用来接受用户命令(用数字表示命令),并把命令写入一个队列中
另一个线程从队列中读取命令,解析命令并执行一系列操作
程序中共享数据(数据结构) msg_que,消息队列同一时刻只允许一个线程进行操作(读取或写入)。因此 msg_que 是临界资源,我们需要对访问临界资源的代码加锁。
我们可以用 std::lock_guard 代替原先代码中的 lock() 和 unlock() 部分。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#include
可能的运行结果如下: