【CPP】C++后端开发面试:深入理解编程中的锁机制

ops/2025/2/8 21:50:49/

文章目录

    • 1. 互斥锁(Mutex)
      • 1.1 基本概念
      • 1.2 特点
      • 1.3 应用场景
      • 1.4 示例代码
    • 2. 递归锁(Recursive Mutex)
      • 2.1 基本概念
      • 2.2 特点
      • 2.3 应用场景
      • 2.4 示例代码
    • 3. 读写锁(Read-Write Lock)
      • 3.1 基本概念
      • 3.2 特点
      • 3.3 应用场景
      • 3.4 示例代码
    • 4. 自旋锁(Spinlock)
      • 4.1 基本概念
      • 4.2 特点
      • 4.3 应用场景
      • 4.4 示例代码
    • 5. 条件变量(Condition Variable)
      • 5.1 基本概念
      • 5.2 特点
      • 5.3 应用场景
      • 5.4 示例代码
    • 6. 总结
  • 高级锁
    • 1. 悲观锁(Pessimistic Locking)
      • 1.1 基本概念
      • 1.2 特点
      • 1.3 应用场景
      • 1.4 示例代码
    • 2. 乐观锁(Optimistic Locking)
      • 2.1 基本概念
      • 2.2 特点
      • 2.3 应用场景
      • 2.4 示例代码
    • 3. 公平锁(Fair Lock)与非公平锁(Non-Fair Lock)
      • 3.1 基本概念
      • 3.2 特点
      • 3.3 应用场景
      • 3.4 示例代码
    • 4. CAS操作(Compare-And-Swap)
      • 4.1 基本概念
      • 4.2 特点
      • 4.3 应用场景
      • 4.4 示例代码
    • 5. 总结

在这里插入图片描述

在多线程编程中,锁是确保线程安全的重要工具。不同的锁机制适用于不同的场景,理解它们的区别和应用场景对于编写高效、安全的多线程程序至关重要。本文将详细介绍几种常见的锁机制,并探讨它们的区别、侧重点及应用场景。

1. 互斥锁(Mutex)

1.1 基本概念

互斥锁(Mutex)是最常见的锁机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。当一个线程持有互斥锁时,其他线程必须等待锁被释放后才能获取锁并访问资源。

1.2 特点

  • 独占性:同一时间只有一个线程可以持有锁。
  • 阻塞:如果锁已被其他线程持有,当前线程会被阻塞,直到锁被释放。
  • 非递归:标准互斥锁不支持递归加锁,即同一个线程不能多次获取同一个锁。

1.3 应用场景

  • 保护共享数据的读写操作。
  • 适用于临界区较小的场景,以避免长时间阻塞其他线程。

1.4 示例代码

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void increment() {mtx.lock();++shared_data;mtx.unlock();
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Shared data: " << shared_data << std::endl;return 0;
}

2. 递归锁(Recursive Mutex)

2.1 基本概念

递归锁允许同一个线程多次获取同一个锁,而不会导致死锁。每次获取锁后,必须释放相同次数的锁才能完全释放资源。

2.2 特点

  • 递归性:同一个线程可以多次获取同一个锁。
  • 阻塞:与互斥锁类似,其他线程会被阻塞,直到锁被完全释放。

2.3 应用场景

  • 适用于可能递归调用同一个函数的场景。
  • 需要多次加锁的复杂逻辑。

2.4 示例代码

#include <iostream>
#include <thread>
#include <mutex>std::recursive_mutex rmtx;
int shared_data = 0;void recursive_increment(int count) {if (count <= 0) return;rmtx.lock();++shared_data;recursive_increment(count - 1);rmtx.unlock();
}int main() {std::thread t1(recursive_increment, 3);std::thread t2(recursive_increment, 3);t1.join();t2.join();std::cout << "Shared data: " << shared_data << std::endl;return 0;
}

3. 读写锁(Read-Write Lock)

3.1 基本概念

读写锁允许多个线程同时读取共享资源,但在写操作时需要独占锁。这种锁机制在读多写少的场景中非常高效。

3.2 特点

  • 读共享:多个线程可以同时获取读锁。
  • 写独占:写操作需要独占锁,其他线程无法获取读锁或写锁。
  • 优先级:通常写锁优先级高于读锁,以避免写操作被长时间阻塞。

3.3 应用场景

  • 读多写少的场景,如缓存系统、数据库访问等。
  • 需要高并发读取数据的场景。

3.4 示例代码

#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_mutex smtx;
int shared_data = 0;void read_data() {smtx.lock_shared();std::cout << "Read data: " << shared_data << std::endl;smtx.unlock_shared();
}void write_data(int value) {smtx.lock();shared_data = value;smtx.unlock();
}int main() {std::thread t1(read_data);std::thread t2(write_data, 10);std::thread t3(read_data);t1.join();t2.join();t3.join();return 0;
}

4. 自旋锁(Spinlock)

4.1 基本概念

自旋锁是一种忙等待锁,当线程尝试获取锁时,如果锁已被其他线程持有,当前线程会一直循环检查锁的状态,直到锁被释放。

4.2 特点

  • 忙等待:线程不会进入睡眠状态,而是不断尝试获取锁。
  • 低延迟:适用于锁持有时间非常短的场景,避免了线程切换的开销。
  • 高CPU占用:由于忙等待,自旋锁会占用大量CPU资源。

4.3 应用场景

  • 锁持有时间非常短的场景。
  • 实时系统或对延迟敏感的场景。

4.4 示例代码

#include <iostream>
#include <thread>
#include <atomic>std::atomic_flag spinlock = ATOMIC_FLAG_INIT;
int shared_data = 0;void increment() {while (spinlock.test_and_set(std::memory_order_acquire)) {// 自旋等待}++shared_data;spinlock.clear(std::memory_order_release);
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Shared data: " << shared_data << std::endl;return 0;
}

5. 条件变量(Condition Variable)

5.1 基本概念

条件变量用于线程间的同步,允许线程在某个条件不满足时进入等待状态,直到其他线程通知条件满足。

5.2 特点

  • 等待/通知机制:线程可以等待某个条件成立,其他线程可以在条件满足时通知等待的线程。
  • 与互斥锁配合使用:通常与互斥锁一起使用,以确保条件检查的原子性。

5.3 应用场景

  • 生产者-消费者模型。
  • 线程间需要等待特定条件的场景。

5.4 示例代码

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>std::mutex mtx;
std::condition_variable cv;
bool ready = false;void wait_for_ready() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Ready!" << std::endl;
}void set_ready() {std::this_thread::sleep_for(std::chrono::seconds(1));{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_all();
}int main() {std::thread t1(wait_for_ready);std::thread t2(set_ready);t1.join();t2.join();return 0;
}

6. 总结

在C++后端开发中,选择合适的锁机制对于保证多线程程序的正确性和性能至关重要。不同的锁机制有不同的特点和应用场景:

  • 互斥锁:适用于简单的临界区保护。
  • 递归锁:适用于可能递归调用的场景。
  • 读写锁:适用于读多写少的场景。
  • 自旋锁:适用于锁持有时间非常短的场景。
  • 条件变量:适用于线程间需要等待特定条件的场景。

理解这些锁机制的区别和应用场景,可以帮助开发者编写出更高效、更安全的多线程程序。在实际开发中,应根据具体需求选择合适的锁机制,并注意避免死锁、竞争条件等常见问题。

高级锁

在多线程编程中,锁机制是确保线程安全的核心工具。除了常见的互斥锁、读写锁等,还有一些高级锁机制和并发控制策略,如悲观锁、乐观锁、公平锁、非公平锁以及CAS(Compare-And-Swap)操作。这些机制在不同的场景下有着独特的优势和适用性。本文将详细介绍这些概念,并探讨它们的区别、侧重点及应用场景。


1. 悲观锁(Pessimistic Locking)

1.1 基本概念

悲观锁是一种保守的并发控制策略,它假设在多线程环境下,共享资源很可能会被其他线程修改,因此在访问资源时,会先加锁,确保资源不会被其他线程修改。

1.2 特点

  • 先加锁,后操作:在访问共享资源之前,先获取锁,确保资源独占。
  • 阻塞:如果锁已被其他线程持有,当前线程会被阻塞,直到锁被释放。
  • 适合写多读少的场景:由于悲观锁会阻塞其他线程,适合写操作频繁的场景。

1.3 应用场景

  • 数据库中的行级锁、表级锁。
  • 多线程环境下对共享资源的写操作。

1.4 示例代码

#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx;
int shared_data = 0;void increment() {mtx.lock(); // 悲观锁:先加锁++shared_data;mtx.unlock(); // 操作完成后释放锁
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Shared data: " << shared_data << std::endl;return 0;
}

2. 乐观锁(Optimistic Locking)

2.1 基本概念

乐观锁是一种乐观的并发控制策略,它假设在多线程环境下,共享资源不太可能被其他线程修改,因此在访问资源时不会加锁,而是在提交操作时检查资源是否被修改。

2.2 特点

  • 先操作,后检查:在访问共享资源时不加锁,但在提交操作时检查资源是否被修改。
  • 无阻塞:不会阻塞其他线程,适合读多写少的场景。
  • 冲突处理:如果检测到冲突(资源被修改),需要回滚操作并重试。

2.3 应用场景

  • 数据库中的版本控制(如使用版本号或时间戳)。
  • 读多写少的场景,如缓存系统。

2.4 示例代码

#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> shared_data(0);void increment() {int old_value = shared_data.load();while (!shared_data.compare_exchange_weak(old_value, old_value + 1)) {// 如果冲突,重试}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Shared data: " << shared_data << std::endl;return 0;
}

3. 公平锁(Fair Lock)与非公平锁(Non-Fair Lock)

3.1 基本概念

  • 公平锁:按照线程请求锁的顺序分配锁,先到先得。
  • 非公平锁:不保证线程获取锁的顺序,可能导致某些线程长时间无法获取锁。

3.2 特点

  • 公平锁
    • 保证线程获取锁的顺序与请求顺序一致。
    • 可能降低吞吐量,因为需要维护一个队列。
  • 非公平锁
    • 不保证顺序,可能导致某些线程“饥饿”。
    • 通常具有更高的吞吐量。

3.3 应用场景

  • 公平锁:适用于需要严格顺序的场景,如任务调度。
  • 非公平锁:适用于高吞吐量的场景,如大多数并发编程场景。

3.4 示例代码

#include <iostream>
#include <thread>
#include <mutex>
#include <queue>std::mutex mtx;
std::queue<int> task_queue;void fair_task(int id) {std::unique_lock<std::mutex> lock(mtx); // 公平锁std::cout << "Task " << id << " is running." << std::endl;
}int main() {std::thread t1(fair_task, 1);std::thread t2(fair_task, 2);t1.join();t2.join();return 0;
}

4. CAS操作(Compare-And-Swap)

4.1 基本概念

CAS是一种无锁编程技术,通过原子操作实现并发控制。它包含三个操作数:内存位置、期望值和新值。只有当内存位置的当前值等于期望值时,才会将新值写入内存位置。

4.2 特点

  • 原子性:CAS操作是原子的,不会被其他线程打断。
  • 无锁:不需要加锁,适合高并发场景。
  • 重试机制:如果CAS失败,需要重试。

4.3 应用场景

  • 实现无锁数据结构,如无锁队列、无锁栈。
  • 高并发场景下的计数器、标志位更新。

4.4 示例代码

#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);void increment() {int old_value = counter.load();while (!counter.compare_exchange_weak(old_value, old_value + 1)) {// 如果CAS失败,重试}
}int main() {std::thread t1(increment);std::thread t2(increment);t1.join();t2.join();std::cout << "Counter: " << counter << std::endl;return 0;
}

5. 总结

在多线程编程中,选择合适的锁机制和并发控制策略是确保程序正确性和性能的关键。以下是本文提到的几种机制的总结:

  • 悲观锁:适合写多读少的场景,先加锁后操作。
  • 乐观锁:适合读多写少的场景,先操作后检查。
  • 公平锁:保证线程获取锁的顺序,适合需要严格顺序的场景。
  • 非公平锁:不保证顺序,适合高吞吐量的场景。
  • CAS操作:无锁编程技术,适合高并发场景。

在实际开发中,应根据具体需求选择合适的机制,并注意避免死锁、竞争条件等问题。通过合理使用这些锁机制和并发控制策略,可以编写出高效、安全的多线程程序。


http://www.ppmy.cn/ops/156814.html

相关文章

设计模式---观察者模式

设计模式—观察者模式 定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 主要解决的问题&#xff1a;一个对象状态改变给其他对象通知的问题&#xff0c;而且要考虑到易用和低耦合&#xff0c;…

设计模式学习(四)

行为模式 观察者模式&#xff08;Observer Pattern&#xff09; 定义 它定义了对象之间的一对多依赖关系。当一个对象&#xff08;被观察者&#xff09;的状态发生变化时&#xff0c;所有依赖它的对象&#xff08;观察者&#xff09;都会收到通知并自动更新。 观察者模式的…

使用PHPStudy搭建Cloudreve网盘服务

文章目录 1、前言2、本地网站搭建 2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布 3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&…

超详细UE4(虚幻4)第一人称射击(FPS)游戏制作教程

超详细UE4(虚幻4)第一人称射击(FPS)游戏制作教程 引言 在游戏开发领域,第一人称射击(FPS)游戏一直是最受欢迎的类型之一。从经典的《反恐精英》(CS)到现代的《使命召唤》(Call of Duty),FPS游戏凭借其紧张刺激的游戏体验和高度沉浸感,吸引了无数玩家。如果你是一…

SQL带外注入

SQL 带外注入&#xff08;Out-of-Band SQL Injection, OOB SQLi&#xff09; 是 SQL 注入的一种特殊类型&#xff0c;主要用于以下情况&#xff1a; 数据库没有直接返回错误信息&#xff08;比如被防火墙拦截了&#xff09;。无法使用常规注入手法&#xff08;如 UNION、错误信…

git:恢复纯版本库

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

Linux:基础IO(二.缓冲区、模拟一下缓冲区、详细讲解文件系统)

目录 1. 缓冲区 1.1 概念 1.2 作用与意义 2. 语言级别的缓冲区 2.1 刷新策略 2.2 具体在哪里 2.3 支持格式化 3. 自己来模拟一下缓冲区 3.1 项目文件规划 3.2 mystandard.h 3.3 mystandard.c 3.4 main.c 4.文件系统 4.1磁盘机械结构 4.2磁盘的物理存储 4.3磁盘的…

分享2款 .NET 开源且强大的翻译工具

前言 对于程序员而言永远都无法逃避和英文打交道&#xff0c;今天大姚给大家分享2款 .NET 开源、功能强大的翻译工具&#xff0c;希望可以帮助到有需要的同学。 STranslate STranslate是一款由WPF开源的、免费的&#xff08;MIT License&#xff09;、即开即用、即用即走的翻…