文章目录
- 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. 总结
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/71ffbc353d444706881ec76dd81084f0.png)
在多线程编程中,锁是确保线程安全的重要工具。不同的锁机制适用于不同的场景,理解它们的区别和应用场景对于编写高效、安全的多线程程序至关重要。本文将详细介绍几种常见的锁机制,并探讨它们的区别、侧重点及应用场景。
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操作:无锁编程技术,适合高并发场景。
在实际开发中,应根据具体需求选择合适的机制,并注意避免死锁、竞争条件等问题。通过合理使用这些锁机制和并发控制策略,可以编写出高效、安全的多线程程序。