(三)互斥锁与同步机制

0 阅读3分钟

std::mutex 基础互斥锁

1.互斥锁的基本概念

互斥锁(Mutual Exclusion) 是保护共享资源最基本的同步机制,确保同一时间只有一个线程可以访问临界区。

mutex 除了lock 外,还支持trylock,这两个的区别就是一个阻塞,一个不阻塞,例如我们有两个线程A和B,两个线程同时被调用,线程A获取到了mutex锁,那线程B如果是lock,那它就会等待直到他获取到锁,二如果是trylock,那线程B一看获取不到,就直接跳出了,就不会继续等待。

image.png

image.png

std::lock_mutex 自动锁

RAII风格的锁管理

std::lock_guard 利用RAII(Resource Acquisition Is Initiallzation)模式,在构造时自动加锁,析构时自动解锁,避免忘记解锁。

语法:std::lock_guardstd::mutex lock(mutex);

std::lock_guard是最常用的一个功能,我们往往并不是单独使用Mutex,因为手动管理lock和unlock并不符合我们的RAII思想。

std::lock_guard需要注意的一个点就是,你可以巧妙的利用生命周期来控制锁的范围。

image.png

std::unique_lock 灵活锁

1.unique_lock 的高级特性

unique_lock 是一个更灵活的互斥量封装器,它提供了更多的控制选项,比如延迟锁定、尝试锁、递归锁定、定时锁定等。于std::lock_guard相比,std::unique_lock提供了更多的功能,但也需要更多的管理责任。

2.为什么会有unique_lock?

因为mutex在管理方面有瑕疵,因此出现了一个互斥量封装器lock_guard来智能的管理mutex。但是lock_guard只是简单的管理,功能比较弱,有瑕疵。因此需要搞出来一个功能更强大的东西出来,而这个东西就是unique_lock。

image.png

本质上来说,lock_guard和unique_lock都是为了更好的使用各种锁而诞生的,但是unique_lock更为灵活,功能更为强大,可做的操作比较多。

image.png

unique_lock 与条件变量配合

unique_lock 可以与条件变量配合,实现生产者消费者有锁队列。 这是lock_guard无法实现的功能,也是他们两个的本质区别。

std::shared_mutex 读写锁

std::shared_mutex(C++17)允许多个读操作同时进行,但写操作需要独占访问,适用于读多写少的场景。

简单来说呢,以我们机器视觉行业的工业相机采集场景来说吧,我们有一个内存数据结构,存储了图像数据,工业相机在触发出图回调时,会在回调函数内将图像数据写入到这个内存,这里就是写的场景,它需要加std::unique_lock,例如我们有两个工业相机,他们有可能并发的往这一个数据写,那我们肯定不能让他们冲突呀,所以要用std::unique_lock。

然后我们软件界面会从这个内存读取图片,读图片是不改变这个内存的,所以可以加std::shared_mutex 读写锁,假设我们软件界面有两个线程会读这个内存,std::shared_mutex 读写锁是允许他们并发的读取的。这样可以让读的效率提高一些。

递归锁 std::recursive_mutex

递归锁的使用场景

std::recursive_mutex 允许同一个线程多次获取同一个锁,避免自死锁。 这个不常用。 例如我们有些场景本身就需要递归的进行,同时还需要加锁,那你就可以用递归锁了。

总结

本章详细介绍了C++中的各种互斥锁和同步机制:

  1. std::mutex:基础互斥锁,提供基本的线程同步
  2. std::lock_guard:RAII风格的自动锁管理,避免忘记解锁
  3. std::unique_lock:灵活的锁控制,支持延迟锁定和条件变量
  4. std::shared_mutex:读写锁,优化读多写少的场景
  5. std::recursive_mutex:递归锁,解决同一线程多次加锁的需求