C++中的各种锁
在多线程开发中,经常会遇到数据同步,很多情况下用锁都是一个很好的选择。C++中常用的锁主要有下面几种:
互斥锁(std::mutex)
- 这是最基本的一种锁。它用于保护共享资源,在任意时刻,最多只有一个线程可以获取该锁,从而访问被保护的资源。当一个线程获取了互斥锁后,其他试图获取该锁的线程会被阻塞,直到持有锁的线程释放它。
- 例如,在一个多线程程序中,如果多个线程需要访问和修改同一个全局变量,就可以使用互斥锁来确保在同一时间只有一个线程能够进行修改操作,避免数据竞争导致的错误结果。
1 #include <iostream>
2 #include <mutex>
3 #include <thread>
4
5 std::mutex m;
6 int counter = 0;
7
8 void increment() {
9 m.lock();
10 counter++;
11 std::cout << "Counter value in thread " << std::this_thread::get_id() << " is " << counter << std::endl;
12 m.unlock();
13 }
14
15 int main() {
16 std::thread t1(increment);
17 std::thread t2(increment);
18 t1.join();
19 t2.join();
20 return 0;
21 }
递归互斥锁(std::recursive_mutex)
- 递归互斥锁允许同一个线程多次获取该锁。它内部会记录锁的获取次数,每获取一次,计数加 1,每次释放锁时,计数减 1,当计数为 0 时,锁才真正被释放,可供其他线程获取。
- 假设在一个复杂的函数调用链中,函数 A 调用函数 B,函数 B 又调用函数 A,并且这些函数都需要访问同一个受保护的资源。如果使用普通互斥锁,就会出现死锁,而递归互斥锁就可以避免这种情况,因为它允许同一线程多次获取锁。
1 #include <iostream>
2 #include <mutex>
3 #include <thread>
4
5 std::recursive_mutex rm;
6
7 void recursiveFunction(int count) {
8 rm.lock();
9 if (count > 0) {
10 std::cout << "Recursive call with count = " << count << std::endl;
11 recursiveFunction(count - 1);
12 }
13 rm.unlock();
14 }
15
16 int main() {
17 std::thread t(recursiveFunction, 3);
18 t.join();
19 return 0;
20 }
读写锁(std::shared_mutex) C++17开始才有
- 读写锁主要用于区分对共享资源的读操作和写操作。它有两种获取模式:共享模式(读模式)和独占模式(写模式)。
- 多个线程可以同时以共享模式获取读写锁,这意味着它们可以同时读取共享资源,而不会相互干扰。但是,当一个线程要以独占模式获取读写锁(进行写操作)时,其他线程(无论是读操作还是写操作)都不能获取该锁,直到写操作完成并释放锁。这种机制在有大量读操作和少量写操作的场景下,可以提高程序的并发性能。例如,在一个缓存系统中,多个线程可能经常读取缓存中的数据,只有在缓存数据需要更新时才会进行写操作,使用读写锁可以很好地处理这种情况。
1 #include <iostream>
2 #include <shared_mutex>
3 #include <thread>
4 #include <vector>
5
6 std::shared_mutex smtx;
7 int shared_data = 0;
8
9 void read_data() {
10 std::shared_lock<std::shared_mutex> lock(smtx);
11 std::cout << "Read data: " << shared_data << std::endl;
12 }
13
14 void write_data(int new_value) {
15 std::unique_lock<std::shared_mutex> lock(smtx);
16 shared_data = new_value;
17 std::cout << "Wrote data: " << shared_data << std::endl;
18 }
19
20 int main() {
21 std::vector<std::thread> read_threads;
22 for (int i = 0; i < 5; i++) {
23 read_threads.push_back(std::thread(read_data));
24 }
25 std::thread write_thread(write_data, 10);
26 for (auto& t : read_threads) {
27 t.join();
28 }
29 write_thread.join();
30 return 0;
31 }
定时互斥锁(std::timed_mutex)
- 定时互斥锁是
std::mutex的扩展。除了具备std::mutex的基本功能外,它还允许线程在尝试获取锁时设置一个超时时间。 - 如果在规定的超时时间内无法获取锁,线程不会一直等待,而是可以执行其他操作或者返回错误信息。这在一些对时间敏感的场景中非常有用,比如在一个实时系统中,线程不能因为等待一个锁而无限期地阻塞,需要在一定时间后放弃获取并进行其他处理。
1 #include <iostream>
2 #include <chrono>
3 #include <thread>
4 #include <mutex>
5
6 std::timed_mutex tm;
7
8 void tryLockFunction() {
9 if (tm.try_lock_for(std::chrono::seconds(1))) {
10 std::cout << "Acquired lock" << std::endl;
11 std::this_thread::sleep_for(std::chrono::seconds(2));
12 tm.unlock();
13 } else {
14 std::cout << "Could not acquire lock in time" << std::endl;
15 }
16 }
17
18 int main() {
19 std::thread t1(tryLockFunction);
20 std::thread t2(tryLockFunction);
21 t1.join();
22 t2.join();
23 return 0;
24 }
递归定时互斥锁(std::recursive_timed_mutex)
- 这是结合了递归互斥锁和定时互斥锁特点的一种锁。它允许同一线程多次获取锁,并且在获取锁时可以设置超时时间。
- 当一个线程多次获取这种锁后,需要释放相同次数的锁,锁才会真正被释放,并且在获取锁的过程中,如果在超时时间内无法获取,线程可以采取相应的措施。
1 #include <iostream>
2 #include <chrono>
3 #include <thread>
4 #include <mutex>
5
6 std::recursive_timed_mutex rtm;
7
8 void recursiveTryLockFunction(int count) {
9 if (rtm.try_lock_for(std::chrono::seconds(1))) {
10 std::cout << "Recursive acquired lock, count = " << count << std::endl;
11 if (count > 0) {
12 recursiveTryLockFunction(count - 1);
13 }
14 rtm.unlock();
15 } else {
16 std::cout << "Could not recursively acquire lock in time" << std::endl;
17 }
18 }
19
20 int main() {
21 std::thread t(recursiveTryLockFunction, 3);
22 t.join();
23 return 0;
24 }
自旋锁(通常用std::atomic_flag实现)
- 自旋锁是一种忙等待的锁机制。当一个线程尝试获取自旋锁而锁已经被占用时,这个线程不会进入阻塞状态,而是会不断地检查(“自旋”)锁是否已经被释放。
- 自旋锁在等待时间较短的情况下可能会有比较好的性能表现,因为它避免了线程切换的开销。但是,如果等待时间过长,由于线程一直在占用 CPU 资源进行检查,会导致 CPU 资源的浪费。一般在底层代码或者对性能要求极高、等待时间预计很短的场景下使用。
1 #include <iostream>
2 #include <atomic>
3 #include <thread>
4
5 std::atomic_flag spinLock = ATOMIC_FLAG_INIT;
6
7 void criticalSection() {
8 while (spinLock.test_and_set()) {
9 // 自旋等待
10 }
11 std::cout << "Entered critical section" << std::endl;
12 // 临界区操作
13 spinLock.clear();
14 }
15
16 int main() {
17 std::thread t1(criticalSection);
18 std::thread t2(criticalSection);
19 t1.join();
20 t2.join();
21 return 0;
22 }
条件变量(std::condition_variable)配合互斥锁用于同步(严格来说条件变量不是锁,但常一起用于线程同步场景)
- 条件变量本身不是一种锁,但它通常和互斥锁一起使用,用于实现线程间的同步。它可以让一个线程等待某个条件满足后再继续执行。
- 例如,一个生产者 - 消费者模型中,消费者线程在缓冲区为空时可以使用条件变量等待,直到生产者线程生产出产品并通知消费者线程,这个过程中互斥锁用于保护缓冲区这个共享资源,条件变量用于实现线程间的通信和同步。
1 #include <iostream>
2 #include <thread>
3 #include <mutex>
4 #include <condition_variable>
5 #include <queue>
6
7 std::mutex mtx;
8 std::condition_variable cv;
9 std::queue<int> buffer;
10 const int bufferSize = 5;
11
12 void producer() {
13 for (int i = 0; i < 10; ++i) {
14 std::unique_lock<std::mutex> lock(mtx);
15 while (buffer.size() == bufferSize) {
16 cv.wait(lock);
17 }
18 buffer.push(i);
19 std::cout << "Produced: " << i << std::endl;
20 cv.notify_all();
21 }
22 }
23
24 void consumer() {
25 for (int i = 0; i < 10; ++i) {
26 std::unique_lock<std::mutex> lock(mtx);
27 while (buffer.empty()) {
28 cv.wait(lock);
29 }
30 int data = buffer.front();
31 buffer.pop();
32 std::cout << "Consumed: " << data << std::endl;
33 cv.notify_all();
34 }
35 }
36
37 int main() {
38 std::thread producerThread(producer);
39 std::thread consumerThread(consumer);
40 producerThread.join();
41 consumerThread.join();
42 return 0;
43 }
C++中的各种锁的更多相关文章
- Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock
在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ...
- 在 Java 中高效使用锁的技巧--转载
竞争锁是造成多线程应用程序性能瓶颈的主要原因 区分竞争锁和非竞争锁对性能的影响非常重要.如果一个锁自始至终只被一个线程使用,那么 JVM 有能力优化它带来的绝大部分损耗.如果一个锁被多个线程使用过,但 ...
- 分门别类总结Java中的各种锁,让你彻底记住
概念 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,会造成优先级反转或者饥 ...
- SQLSERVER中的元数据锁
SQLSERVER中的元数据锁 网上对于元数据锁的资料真的非常少 元数据锁一般会出现在DDL语句里 下面列出数据库引擎可以锁定的资源 资源 说明 RID 用于锁定堆(heap)中的某一行 KEY 用于 ...
- 编写高质量代码改善C#程序的157个建议——建议89:在并行方法体中谨慎使用锁
建议89:在并行方法体中谨慎使用锁 除了建议88所提到的场合,要谨慎使用并行的情况还包括:某些本身就需要同步运行的场合,或者需要较长时间锁定共享资源的场合. 在对整型数据进行同步操作时,可以使用静态类 ...
- Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
Java 中15种锁的介绍 Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等,在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类 ...
- Java中的各种锁--分类总结
前言 本文需要具备一定的多线程基础才能更好的理解. 学习java多线程时,最头疼的知识点之一就是java中的锁了,什么互斥锁.排它锁.自旋锁.死锁.活锁等等,细分的话可以罗列出20种左右的锁,光是看着 ...
- mysql中的乐观锁和悲观锁
mysql中的乐观锁和悲观锁的简介以及如何简单运用. 关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的. mysql的悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括 ...
- 轻松搞懂Java中的自旋锁
前言 在之前的文章<一文彻底搞懂面试中常问的各种“锁”>中介绍了Java中的各种“锁”,可能对于不是很了解这些概念的同学来说会觉得有点绕,所以我决定拆分出来,逐步详细的介绍一下这些锁的来龙 ...
- 浅谈Linux中的各种锁及其基本原理
本文首发于:https://mp.weixin.qq.com/s/Ahb4QOnxvb2RpCJ3o7RNwg 微信公众号:后端技术指南针 0.概述 通过本文将了解到如下内容: Linux系统的并行性 ...
随机推荐
- Java类和对象 小白版
一.类 一.类的定义 具有同种属性的对象称为类.定义了它所包含的全体对象的公共特征和功能,对象就是类的一个实例化. 类的三种常见成员:属性.方法.构造器 二.类的编写 1.类名的定义: 2.类属性(特 ...
- 9组-Alpha冲刺-6/4
一.基本情况 队名:不行就摆了吧 组长博客:https://www.cnblogs.com/Microsoft-hc/p/15546712.html 小组人数: 8 二.冲刺概况汇报 卢浩玮 过去两天 ...
- dubbo序列化问题(二)hession2与kryo切换
转
dubbo提供了好几种序列化方式,一般我们都是用的是默认的hession2,而dubbox为我们增加了kryo和fst许了方式,主要体现在速度快,占用内存小,然后我们将序列化配置改为是用kryo: & ...
- 2.3 Exception model
2.3.1 异常状态 2.3.1 异常类型
- Linux程序之可变参数&&选项那些事!
一.linux应用程序如何接收参数? 1. argc.argv Linux应用程序执行时,我们往往通过命令行带入参数给程序,比如 ls /dev/ -l 其中参数 /dev/ .-l都是作为参数传递给 ...
- 零基础学习人工智能—Python—Pytorch学习(五)
前言 上文有一些文字打错了,已经进行了修正. 本文主要介绍训练模型和使用模型预测数据,本文使用了一些numpy与tensor的转换,忘记的可以第二课的基础一起看. 线性回归 结合numpy使用 首先使 ...
- 解决 Rust WebAssembly 启动 Web 程序报错
当你艰难入门 Rust ,并满怀斗志准备投身 WebAssembly,第一课也许会先给你泼盆凉水. 跟随 <Rust 和 WebAssembly> 文档的指引,一路 install.cod ...
- C# Winform 使用 BarTender打印条码
目录 - 1. 使用软件BarTender 设计打印模板 - 2. Winfrom集成打印 - 3.最终效果 - 1. 使用软件BarTender 设计打印模板 贴一个入门级使用教程:https:// ...
- Java 读取命令行输入
在 Java 中,您可以使用 Scanner 类从命令行读取输入.这个类属于 java.util 包,因此在使用之前您需要导入该包. 下面是一个如何从命令行读取输入的 Java 程序示例: impor ...
- 使用Golang的协程竟然变慢了|100万个协程的归并排序耗时分析
前言 这篇文章将用三个版本的归并排序,为大家分析使用协程排序的时间开销(被排序的切片长度由128到1000w) 本期demo地址:https://github.com/BaiZe1998/go-lea ...