目录
1.基本互斥锁(std::mutex)
2.递归互斥锁(std::recursive_mutex)
3.带超时机制的互斥锁(std::timed_mutex)
4.带超时机制的递归互斥锁(std::recursive_timed_mutex)
7.自旋锁
8.总结
1.基本互斥锁(std::mutex)
含义: std::mutex
是最基本的互斥锁,主要用于保护临界区,确保同一时间只有一个线程可以访问共享资源。
使用场景: 当需要保护共享资源不被多个线程同时修改时使用。
特点:简单易用,适用于大多数场景;不能递归锁定,同一线程多次尝试锁定会导致死锁。
以下是一个简单的示例,展示了如何使用 std::mutex
来保护共享数据:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx; //全局互斥锁
int shared_data = 0; //共享数据
void increment_shared_data(int n) {
for (int i = 0; i < n; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
}
}
int main() {
std::thread t1(increment_shared_data, 1000);
std::thread t2(increment_shared_data, 1000);
t1.join();
t2.join();
std::cout << "Shared data: " << shared_data << std::endl;
return 0;
}
这个程序创建了2个线程,每个线程尝试对counter
增加10000次。通过使用std::mutex
, 我们确保每次只有一个线程可以增加计数器,避免了数据竞争。
2.递归互斥锁(std::recursive_mutex)
含义:std::recursive_mutex
允许同一线程多次获取锁而不会发生死锁,这对于递归函数或需要多次锁定的场景非常有用。
使用场景: 在递归函数中需要多次获取同一个锁的情况。
特点:适用于递归调用和需要多次锁定的场景;需要注意避免滥用,因为递归锁的使用会增加锁定次数的复杂性。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
std::recursive_mutex rmtx;
void recursive_function(int depth) {
rmtx.lock();
std::cout << "Depth: " << depth << std::endl;
if (depth > 0) {
recursive_function(depth - 1);
}
rmtx.unlock();
}
int main() {
std::thread t(recursive_function, 5);
t.join();
return 0;
}
这段代码在递归函数recursive_function
中使用std::recursive_mutex
。每次调用都会尝试加锁,由于使用的是递归互斥锁,同一线程可以多次成功获取锁。
3.带超时机制的互斥锁(std::timed_mutex)
含义:std::timed_mutex
在std::mutex
的基础上增加了超时功能,允许线程在指定时间内尝试获取锁,如果在超时时间内未成功获取锁,则返回失败。
使用场景: 当你不希望线程因等待锁而无限期阻塞时使用。
特点:适用于需要设置锁获取超时时间的场景;提供try_lock_for
和try_lock_until
两种超时尝试获取锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex mtx;
void try_lock_function() {
if (mtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Lock acquired!\n";
// 执行受保护的操作
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
mtx.unlock(); // 显式解锁
} else {
std::cout << "Failed to acquire lock within timeout.\n";
}
}
int main() {
std::thread t1(try_lock_function);
std::thread t2(try_lock_function);
t1.join();
t2.join();
return 0;
}
在这个例子中,两个线程都尝试在 1 秒内获取锁。由于互斥锁在同一时刻只能被一个线程持有,因此至少有一个线程将无法在超时时间内获取锁,并输出相应的消息。
4.带超时机制的递归互斥锁(std::recursive_timed_mutex)
含义:std::recursive_timed_mutex
结合了std::recursive_mutex
和std::timed_mutex
的特性,支持递归锁定和超时机制。
使用场景: 适用于需要递归锁定资源,并且希望能够设置尝试获取锁的超时时间的场景。这在需要防止线程在等待锁时无限阻塞的复杂递归调用中特别有用。
特点:适用于递归调用和需要超时机制的场景;提供超时尝试获取递归锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::recursive_timed_mutex mtx;
void recursive_lock_function() {
if (mtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Lock acquired!\n";
// 递归锁定
if (mtx.try_lock_for(std::chrono::seconds(1))) {
std::cout << "Recursive lock acquired!\n";
mtx.unlock(); // 释放递归锁
} else {
std::cout << "Failed to acquire recursive lock within timeout.\n";
}
// ... 执行受保护的操作
mtx.unlock(); // 释放原始锁
} else {
std::cout << "Failed to acquire lock within timeout.\n";
}
}
int main() {
std::thread t1(recursive_lock_function);
std::thread t2(recursive_lock_function);
t1.join();
t2.join();
return 0;
}
请注意,由于 std::recursive_timed_mutex
允许递归锁定,上面的示例中展示了如何在已经持有锁的情况下再次尝试获取锁(尽管在这个特定示例中,第二次尝试获取锁是多余的,因为我们已经持有锁了)。然而,在实际情况中,递归锁定可能用于更复杂的场景,其中函数可能会递归调用自己,并且每个递归调用都需要访问受保护的数据。
5.共享互斥锁也叫读写锁(std::shared_mutex)
含义:std::shared_mutex
允许多个线程同时读取,但只有一个线程可以写入。这在读多写少的场景下非常有用。
使用场景: 适用于读操作远多于写操作的情况。
特点:适用于读多写少的场景;读操作和写操作使用不同的锁定机制。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex shmtx;
void read_shared(int id) {
std::shared_lock<std::shared_mutex> lock(shmtx); // 共享锁
std::cout << "Thread " << id << " is reading" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
void write_shared(int id) {
std::unique_lock<std::shared_mutex> lock(shmtx); // 独占锁
std::cout << "Thread " << id << " is writing" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int main() {
std::thread readers[5], writer(write_shared, 1);
for (int i = 0; i < 5; ++i) {
readers[i] = std::thread(read_shared, i + 2);
}
writer.join();
for (auto& reader : readers) {
reader.join();
}
return 0;
}
输出结果可能会有所不同,因为读写顺序由操作系统的线程调度决定。本例中,一个写线程在修改数据,多个读线程在同时读数据。通过std::shared_mutex
,我们允许多个读操作同时进行,但写操作是独占的。
6.带超时机制的共享互斥锁(std::shared_timed_mutex)
含义:std::shared_timed_mutex
是 C++ 标准库中的一个同步原语,它结合了 std::shared_mutex
(共享互斥锁)和超时机制的特性。std::shared_mutex
允许多个线程同时以共享模式持有锁(即读取操作可以并发执行),但每次只有一个线程能以独占模式持有锁(即写入操作是互斥的)。通过添加超时机制,std::shared_timed_mutex
允许线程尝试以共享模式或独占模式获取锁,并设置一个超时时间,如果在这段时间内未能成功获取锁,则可以放弃并继续执行其他操作。
使用场景:当你不希望线程因等待锁而无限期阻塞时使用。
特点:适用于读多写少且需要超时机制的场景;提供超时尝试获取共享锁的方法。
示例如下:
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <chrono>
std::shared_timed_mutex shtmmtx;
void try_read_shared(int id) {
if (shtmmtx.try_lock_shared_for(std::chrono::milliseconds(100))) {
std::cout << "Thread " << id << " is reading" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
shtmmtx.unlock_shared();
} else {
std::cout << "Thread " << id << " could not read" << std::endl;
}
}
void try_write_shared(int id) {
if (shtmmtx.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "Thread " << id << " is writing" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
shtmmtx.unlock();
} else {
std::cout << "Thread " << id << " could not write" << std::endl;
}
}
int main() {
std::thread readers[5], writer(try_write_shared, 1);
for (int i = 0; i < 5; ++i) {
readers[i] = std::thread(try_read_shared, i + 2);
}
writer.join();
for (auto& reader : readers) {
reader.join();
}
return 0;
}
7.自旋锁
含义:在C++中,自旋锁(spinlock)是一种低级的同步机制,用于保护共享资源,防止多个线程同时访问。与互斥锁(mutex)不同,当自旋锁被锁定时,尝试获取锁的线程会不断循环检查锁是否可用,而不是进入睡眠状态等待锁被释放。这意味着,自旋锁在等待时间很短的情况下是非常有效的,但如果等待时间过长,会导致CPU资源的浪费。
C++标准库本身并不直接提供自旋锁的实现,但你可以使用<atomic>
库中的原子操作来手动实现一个自旋锁,或者使用特定平台提供的API(如Windows的SRWLOCK
或POSIX的pthread_spinlock_t
)。
使用场景:自旋锁适用于锁持有时间非常短且线程不希望在操作系统调度中频繁上下文切换的场景。这通常用在低延迟系统中,或者当线程数量不多于CPU核心数量时,确保CPU不会在等待锁时空闲。
示例如下:
#include <atomic>
#include <iostream>
#include <thread>
#include <chrono>
class Spinlock {
private:
std::atomic_flag lock_ = ATOMIC_FLAG_INIT;
public:
void lock() {
while (lock_.test_and_set(std::memory_order_acquire)) {
// 循环直到锁被释放
}
}
void unlock() {
lock_.clear(std::memory_order_release);
}
bool try_lock() {
return !lock_.test_and_set(std::memory_order_acquire);
}
};
void threadFunction(Spinlock& lock, int id) {
lock.lock();
std::cout << "Thread " << id << " entered critical section\n";
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟耗时操作
std::cout << "Thread " << id << " leaving critical section\n";
lock.unlock();
}
int main() {
Spinlock lock;
std::thread t1(threadFunction, std::ref(lock), 1);
std::thread t2(threadFunction, std::ref(lock), 2);
t1.join();
t2.join();
return 0;
}
在这个例子中,Spinlock
类使用了一个 std::atomic_flag
类型的成员变量 lock_
来实现锁的功能。lock_
的 test_and_set
方法会尝试将标志设置为 true
并返回之前的值。如果返回 false
,表示锁之前未被锁定,当前线程成功获取锁;如果返回 true
,表示锁已被其他线程持有,当前线程需要继续循环等待。
请注意,自旋锁在多核处理器上且等待时间较短时通常表现良好,但在等待时间较长或锁竞争激烈时可能会导致性能问题。因此,在选择使用自旋锁时,需要根据具体的应用场景和性能要求做出合理的选择。
8.总结
C++标准库提供了多种类型的互斥锁,每种锁都有其特定的用途和特点。选择合适的互斥锁类型可以有效提高程序的并发性能和安全性。
C++惯用法之RAII思想: 资源管理_raii 思想-CSDN博客