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

在多线程编程中,锁是确保线程安全的重要工具。不同的锁机制适用于不同的场景,理解它们的区别和应用场景对于编写高效、安全的多线程程序至关重要。本文将详细介绍几种常见的锁机制,并探讨它们的区别、侧重点及应用场景。
1. 互斥锁(Mutex)
1.1 基本概念
互斥锁(Mutex)是最常见的锁机制,用于保护共享资源,确保同一时间只有一个线程可以访问该资源。当一个线程持有互斥锁时,其他线程必须等待锁被释放后才能获取锁并访问资源。
1.2 特点
- 独占性:同一时间只有一个线程可以持有锁。
- 阻塞:如果锁已被其他线程持有,当前线程会被阻塞,直到锁被释放。
- 非递归:标准互斥锁不支持递归加锁,即同一个线程不能多次获取同一个锁。
1.3 应用场景
- 保护共享数据的读写操作。
- 适用于临界区较小的场景,以避免长时间阻塞其他线程。
1.4 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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 示例代码
cpp
#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操作:无锁编程技术,适合高并发场景。
在实际开发中,应根据具体需求选择合适的机制,并注意避免死锁、竞争条件等问题。通过合理使用这些锁机制和并发控制策略,可以编写出高效、安全的多线程程序。