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系统的并行性 ...
随机推荐
- .Net Aspire次体验
上次用上了在微软MVP的带领下用上了Aspire,在开发阶段隐藏了细节,什么都不用做,点个调试按钮就跑起来了,可是部署时出现了难题, 因为发布时只能选择Azure环境,为此注册了Azure,开了科网. ...
- java如何保证一个方法只能执行一次
我们经常会遇到一些情况需要某一个方法或者操作只执行一次,比如说配置信息加载,如果配置信息需要动态刷新,这个不在适用范围.下面列举几种方式 第一种 如果是web容器,可以使用servlet或者Liste ...
- 通过 ob-operator 部署 OceanBase 数据库
本文介绍如何通过 ob-operator 来部署 OceanBase 数据库. 背景信息 ob-operator 与其他 operator 一样,旨在让 OceanBase 以容器的方式,无缝运行在 ...
- AvaloniaChat-v0.0.2:兼容智谱AI 快速使用指南
智谱AI介绍 北京智谱华章科技有限公司(简称"智谱AI")致力于打造新一代认知智能大模型,专注于做大模型的中国创新.公司合作研发了中英双语千亿级超大规模预训练模型GLM-130B, ...
- 代码随想录Day22
77. 组合 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合. 你可以按 任何顺序 返回答案. 示例 1: 输入:n = 4, k = 2 输出: [ [2,4], [ ...
- 讲讲Java的序列化反序列化?
序列化:把对象转换为字节序列的过程称为对象的序列化. 反序列化:把字节序列恢复为对象的过程称为对象的反序列化. 什么时候会用到 当只在本地 JVM 里运行下 Java 实例,这个时候是不需要什么序列化 ...
- TeX Live 安装
Ubuntu sudo apt install texlive-full 其他可用软件包: 软件包 压缩包 磁盘空间 texlive-latex-base 59 MB 216 MB texlive-l ...
- 2023/11/16 NOIP 模拟赛
T1 基于1的算术 标签 暴力枚举 思路1 赛时想了个假的 DP,只拿了 77 分,,, 小于 \(10^{15}\) 的仅由 \(1\) 组成的数只有 \(15\) 个,直接枚举即可. 想了一个做法 ...
- ES6中的Set数据结构
Set是ES6新推出的数据结构,Set结构里面的每个元素都是唯一的: 如何创建一个Set? // Set 构造函数接收一个数组进行初始化;如果什么都不传则创建一个空Set; var set = new ...
- Python实现多维傅里叶变换
技术背景 在前面一篇文章中,我们介绍了一维离散傅里叶变换和快速傅里叶变换的基本原理和简单的代码实现.本文补充一个多维傅里叶变换的场景,以及简单的Python实现. 二维傅里叶变换 首先回顾一下上一篇文 ...