C++标准库中的锁lock_guard、unique_lock、shared_lock、scoped_lock、recursive_mutex

简介: C++标准库中的锁lock_guard、unique_lock、shared_lock、scoped_lock、recursive_mutex

C++标准库中的锁

std::mutex.lock是我们在C++中比较常见的锁,我们使用std::mutex.lock方法时,同时需要考虑何时使用std:mutex.unlock方法去解锁。如果在复杂的多线程情况下,加锁、解锁的时机很难把握,也不好实现。

RAII原则是所有的资源都必须有管理对象,而资源的申请操作在管理对象的构造函数中进行,而资源的回收则在管理对象的析构函数中进行

C++新标准提供了lock_guard, unique_lock, shared_lock, 和 scoped_lock四种锁,用于各种复杂情况。这四种锁都是满足RAII风格。

lock_guard

  1. lock_guard:这是C++11中一个简单的 RAII(Resource Acquisition Is Initialization)风格的锁,用于在作用域内自动管理互斥量的锁定和解锁。当 lock_guard 对象被创建时,它会自动锁定互斥量,当对象离开作用域时,它会自动解锁互斥量。lock_guard 不支持手动锁定和解锁,也不支持条件变量。
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int shared_data = 0;
void increment() {
  std::lock_guard<std::mutex> lock(mtx);
  ++shared_data;
  std::cout << "Incremented shared_data: " << shared_data << std::endl;
}
int main() {
  std::thread t1(increment);
  std::thread t2(increment);
  t1.join();
  t2.join();
  return 0;
}

运行结果:

unique_lock

unique_lock:这是C++11中一个更灵活的锁,它允许手动锁定解锁互斥量,以及与条件变量一起使用(是lock_guard的进阶版)。与 lock_guard 类似,unique_lock 也是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁互斥量。unique_lock 还支持延迟锁定、尝试锁定和可转移的锁所有权。

#include <iostream>
#include <mutex>
#include <thread>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
int shared_data = 0;
bool ready = false;
void increment() {
  std::unique_lock<std::mutex> lock(mtx);
  cv.wait(lock, [] { return ready; });
  ++shared_data;
  std::cout << "Incremented shared_data: " << shared_data << std::endl;
}
void set_ready() {
  std::unique_lock<std::mutex> lock(mtx);
  std::cout << "set_ready finish!" << std::endl;
  ready = true;
  cv.notify_all();
}
int main() {
  std::thread t1(increment);
  std::thread t2(increment);
  std::thread t3(set_ready);
  t1.join();
  t2.join();
  t3.join();
  return 0;
}

运行结果:

shared_lock

  1. shared_lock:C++14引入的锁,这是一个用于共享互斥量(如 std::shared_mutexstd::shared_timed_mutex)的锁,允许多个线程同时读取共享数据,但在写入数据时仍然保证互斥。shared_lock 也是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁共享互斥量。shared_lock 支持手动锁定和解锁,以及尝试锁定。
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <thread>
std::shared_mutex sh_mtx;
int shared_data = 0;
void read_data() {
  std::shared_lock<std::shared_mutex> lock(sh_mtx);
  std::cout << "Read shared_data: " << shared_data << std::endl;
}
void write_data() {
  std::unique_lock<std::shared_mutex> lock(sh_mtx);
  ++shared_data;
  std::cout << "Incremented shared_data: " << shared_data << std::endl;
}
int main() {
  std::thread t1(read_data);
  std::thread t2(write_data);
  std::thread t3(read_data);
  t1.join();
  t2.join();
  t3.join();
  return 0;
}

运行结果:

scoped_lock

scoped_lock:这是 C++17 引入的一个新锁,用于同时锁定多个互斥量,以避免死锁。scoped_lock 是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁所有互斥量。scoped_lock 不支持手动锁定和解锁,也不支持条件变量。它的主要用途是在需要同时锁定多个互斥量时提供简单且安全的解决方案。

#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx1;
std::mutex mtx2;
int shared_data1 = 0;
int shared_data2 = 0;
void increment_both() {
    std::scoped_lock lock(mtx1, mtx2);
    ++shared_data1;
    ++shared_data2;
    std::cout << "Incremented shared_data1: " << shared_data1 << ", shared_data2: " << shared_data2 << std::endl;
}
int main() {
    std::thread t1(increment_both);
    std::thread t2(increment_both);
    t1.join();
    t2.join();
    return 0;
}

互斥量

recursive_mutex

使用场景:一个类的不同成员函数之间,存在相互调用的情况, 如果这样的成员函数作为线程的入口函数时,就会出现在成员函数 func1()中对某个互斥量上锁,并且, func1()中调用了成员函数 func2() ,实际上 func2()为了保护成员数据,func2()内部也对同一个互斥量上锁。 在我们对 std::mutex 的使用经验中, 这样的情况,必定会导致未定义的行为,从而导致死锁的产生。

C++标准库为此提供了 std::recursive_mutex 互斥量, 它在具备 std::mutex 的功能之上, 还可以可以支持上面描述的 “对同一个互斥量进行嵌套上锁” 的能力。

std::recursive_mutex是C++标准库中的一个互斥锁类型,允许同一个线程多次锁定同一个互斥锁,而不会导致死锁。每次锁定互斥锁时,都会增加一个计数器。当解锁互斥锁时,计数器减少。只有当计数器为零时,互斥锁才会被释放,其他线程才能锁定它。

#include <iostream>
#include <mutex>
#include <thread>
std::recursive_mutex mtx;
void print_hello(int n) {
  std::lock_guard<std::recursive_mutex> lock(mtx);
  if (n > 0) {
    std::cout << "Hello, ";
    print_hello(n - 1);
  }
  else {
    std::cout << "world!" << std::endl;
  }
}
int main() {
  std::thread t1(print_hello, 5);
  std::thread t2(print_hello, 3);
  t1.join();
  t2.join();
  return 0;
}

运行结果:

递归运行,同一个线程同一个互斥量的情况可以避免死锁。

目录
相关文章
|
2月前
|
存储 C++
C++的I/O流标准库
C++的I/O流标准库
28 2
|
1月前
|
存储 自然语言处理 安全
C++ STL标准库 《string原理与实战分析》
C++ STL标准库 《string原理与实战分析》
37 0
|
19天前
|
安全 C++
C++一分钟之-互斥锁与条件变量
【6月更文挑战第26天】在C++并发编程中,`std::mutex`提供互斥访问,防止数据竞争,而`std::condition_variable`用于线程间的同步协调。通过`lock_guard`和`unique_lock`防止忘记解锁,避免死锁。条件变量需配合锁使用,确保在正确条件下唤醒线程,注意虚假唤醒和无条件通知。生产者-消费者模型展示了它们的应用。正确使用这些工具能解决同步问题,提升并发性能和可靠性。
24 4
|
12天前
|
存储 算法 程序员
C++基础知识(八:STL标准库(Vectors和list))
C++ STL (Standard Template Library标准模板库) 是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如 queues(队列), lists(链表), 和 stacks(栈)等. STL容器的提供是为了让开发者可以更高效率的去开发,同时我们应该也需要知道他们的底层实现,这样在出现错误的时候我们才知道一些原因,才可以更好的去解决问题。
|
12天前
|
算法 前端开发 C++
C++基础知识(八:STL标准库 deque )
deque在C++的STL(Standard Template Library)中是一个非常强大的容器,它的全称是“Double-Ended Queue”,即双端队列。deque结合了数组和链表的优点,提供了在两端进行高效插入和删除操作的能力,同时保持了随机访问的特性。
|
12天前
|
存储 C++ 索引
C++基础知识(八:STL标准库 Map和multimap )
C++ 标准模板库(STL)中的 map 容器是一种非常有用的关联容器,用于存储键值对(key-value pairs)。在 map 中,每个元素都由一个键和一个值组成,其中键是唯一的,而值则可以重复。
|
1月前
|
C语言 C++
C++初阶学习第六弹——探索STL奥秘(一)——标准库中的string类
C++初阶学习第六弹——探索STL奥秘(一)——标准库中的string类
19 0
|
19天前
|
C++ 容器
C++ STL标准库 《map容器详解》
C++ STL标准库 《map容器详解》
21 0
|
19天前
|
存储 C++ 容器
C++ STL标准库 《map容器详解》
C++ STL标准库 《map容器详解》
24 0
|
2月前
|
存储 C++ 索引
C++标准库容器的使用
C++标准库容器的使用
24 1