C++锁: 读锁,递归锁,超时锁
面试聊到锁,只知道数据库的读锁,不了解编程中的读锁,也没谈,遂败北。
代码分 C++ 和 POSIX 版本
文章目录
- 互斥锁
- 读写锁
- 自旋锁
- 递归锁
- 超时锁
- 原子操作
- 条件变量
- 信号量
互斥锁
互斥锁是最基本的线程同步工具,确保同一时刻只有一个线程可以访问共享资源。
如果加锁失败,锁被别人持有,该线程会阻塞,即不占用CPU资源,直至拿到锁。
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void worker() {
// 使用 lock() 方法,若锁被占用则阻塞
mtx.lock();
std::cout << "Thread " << std::this_thread::get_id() << " has the lock." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id() << " is releasing the lock." << std::endl;
mtx.unlock();
// 使用 try_lock() 方法,若锁被占用则返回 false
if (mtx.try_lock()) {
std::cout << "Thread " << std::this_thread::get_id() << " acquired the lock using try_lock." << std::endl;
mtx.unlock();
} else {
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire the lock using try_lock." << std::endl;
}
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
return 0;
}
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *thread_func(void *arg) {
pthread_mutex_lock(&lock); // 加锁
// 访问共享资源
pthread_mutex_unlock(&lock); // 解锁
return NULL;
}
pthread_mutex_trylock()的方式可以立即返回~
读写锁
读写锁允许多个线程同时读取共享资源,但写操作需要独占。
C++17 是对 shared_mutex 进行 std::shared_lock<> 和 std::unique_ptr<> 这两个lock_guard实现的。
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex rwMutex;
int sharedData = 0;
void reader() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
std::cout << "Reader reads: " << sharedData << std::endl;
}
void writer() {
std::unique_lock<std::shared_mutex> lock(rwMutex);
++sharedData;
std::cout << "Writer writes: " << sharedData << std::endl;
}
int main() {
std::thread r1(reader);
std::thread w1(writer);
r1.join();
w1.join();
return 0;
}
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void *reader(void *arg) {
pthread_rwlock_rdlock(&rwlock); // 读锁
// 读取共享资源
pthread_rwlock_unlock(&rwlock); // 解锁
return NULL;
}
void *writer(void *arg) {
pthread_rwlock_wrlock(&rwlock); // 写锁
// 修改共享资源
pthread_rwlock_unlock(&rwlock); // 解锁
return NULL;
}
自旋锁
线程在获取锁失败时不会进入休眠,而是持续尝试获取锁。
避免了线程上下文切换的开销,适合锁持有时间极短(如几十到几百个 CPU 周期)的场景。多核 CPU 环境且锁竞争不激烈时效果最佳
像互斥锁,会让出CPU,这会交换上下文。最后还要换回来,如果等待时间小于两次交换上下文的时间,那等待就是更高效的。
C++ 模拟 【自旋锁的"忙等待"与"混合模式"的权衡】
#include <iostream>
#include <thread>
#include <atomic>
class SpinLock {
private:
std::atomic<bool> flag = false;
public:
void lock() {
while (flag.exchange(true, std::memory_order_acquire)) {
while (flag.load(std::memory_order_relaxed)) {
std::this_thread::yield();
}
}
}
void unlock() {
flag.store(false, std::memory_order_release);
}
};
SpinLock spinLock;
void spinWorker() {
spinLock.lock();
std::cout << "Spin lock acquired by thread: " << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
spinLock.unlock();
}
int main() {
std::thread s1(spinWorker);
std::thread s2(spinWorker);
s1.join();
s2.join();
return 0;
}
-
exchange是原子类的替换值操作
std::memory_order_acquire:这是内存序的一种,用于保证在 exchange 操作之后的所有读写操作不会被重排到 exchange 操作之前,从而确保在获取锁之后对共享资源的访问是安全的。(不会出现先访问共享资源再获取锁的情况,从而确保了对共享资源的安全访问。)【如果学过计算机系统的MIPS,就应该了解流水线,一个道理】 -
load 函数:std::atomic 类的成员函数,用于获取 flag 的当前值。
std::memory_order_relaxed:这是一种宽松的内存序,只保证原子性,不保证任何顺序性。在这里使用宽松内存序是因为在这个内层循环中,主要目的是快速检查锁的状态,对内存顺序的要求不高,使用宽松内存序可以提高性能。
std::this_thread::yield() 让出 CPU 时间片
#include <pthread.h>
pthread_spinlock_t spinlock;
void *worker(void *arg) {
pthread_spin_lock(&spinlock); // 自旋加锁
// 访问共享资源
pthread_spin_unlock(&spinlock); // 解锁
return NULL;
}
递归锁
初始化的时候设置。
自己调用自己,加同一个锁。
#include <iostream>
#include <thread>
#include <recursive_mutex>
std::recursive_mutex recursiveMutex;
void recursiveWorker(int depth) {
if (depth == 0) return;
std::lock_guard<std::recursive_mutex> lock(recursiveMutex);
std::cout << "Recursive lock acquired at depth " << depth << " by thread: " << std::this_thread::get_id() << std::endl;
recursiveWorker(depth - 1);
}
int main() {
std::thread rw(recursiveWorker, 3);
rw.join();
return 0;
}
POSIX 初始化普通锁:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t lock;
void functionA() {
pthread_mutex_lock(&lock);
printf("Function A: Locked\n");
functionA(); // ✅ 不会死锁,因为递归锁允许同一线程重复加锁
pthread_mutex_unlock(&lock);
printf("Function A: Unlocked\n");
}
int main() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
functionA(); // 正常执行
return 0;
}
超时锁
在尝试获取互斥锁时,如果锁没有在指定的 超时时间内 被获取到,线程就会放弃等待并继续执行。
超时(超过 2 秒)仍未获取到锁,它会返回非 0 值
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
std::timed_mutex tmtx;
void worker() {
// 尝试在 2 秒内获取锁
if (tmtx.try_lock_for(std::chrono::seconds(2))) {
std::cout << "Thread " << std::this_thread::get_id() << " acquired the lock using try_lock_for." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
tmtx.unlock();
} else {
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire the lock using try_lock_for." << std::endl;
}
}
int main() {
std::thread t1(worker);
std::thread t2(worker);
t1.join();
t2.join();
return 0;
}
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
pthread_mutex_t lock;
void* threadFunc(void* arg) {
printf("Thread: Trying to lock...\n");
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 2; // 设置超时时间(2 秒)
if (pthread_mutex_timedlock(&lock, &timeout) == 0) {
printf("Thread: Locked!\n");
pthread_mutex_unlock(&lock);
} else {
printf("Thread: Lock timeout! Giving up.\n");
}
return NULL;
}
int main() {
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock); // 主线程加锁
pthread_t thread;
pthread_create(&thread, NULL, threadFunc, NULL);
sleep(5); // 模拟主线程长时间占有锁
pthread_mutex_unlock(&lock);
pthread_join(thread, NULL);
return 0;
}
原子操作
一定要谈是操作该变量时
#include <iostream>
#include <thread>
#include <atomic>
std::atomic<int> atomic_counter(0);
void atomic_worker() {
for (int i = 0; i < 1000; ++i) {
atomic_counter.fetch_add(1, std::memory_order_relaxed);
}
}
int main() {
std::thread a1(atomic_worker);
std::thread a2(atomic_worker);
a1.join();
a2.join();
std::cout << "Atomic counter value: " << atomic_counter.load() << std::endl;
return 0;
}
#include <stdatomic.h>
atomic_int counter = 0;
void *worker(void *arg) {
atomic_fetch_add(&counter, 1); // 原子递增
return NULL;
}
条件变量
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex cvMutex;
std::condition_variable cv;
bool ready = false;
void waiter() {
std::unique_lock<std::mutex> lock(cvMutex);
cv.wait(lock, [] { return ready; });
std::cout << "Waiter thread is notified." << std::endl;
}
void notifier() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
{
std::lock_guard<std::mutex> lock(cvMutex);
ready = true;
}
cv.notify_one();
std::cout << "Notifier thread sent notification." << std::endl;
}
int main() {
std::thread w(waiter);
std::thread n(notifier);
w.join();
n.join();
return 0;
}
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0; // 共享状态
void *consumer(void *arg) {
pthread_mutex_lock(&mutex);
while (ready == 0) {
pthread_cond_wait(&cond, &mutex); // 等待条件满足
}
// 处理数据
pthread_mutex_unlock(&mutex);
return NULL;
}
void *producer(void *arg) {
pthread_mutex_lock(&mutex);
ready = 1;
pthread_cond_signal(&cond); // 唤醒等待线程
pthread_mutex_unlock(&mutex);
return NULL;
}
信号量
信号量可以控制多个线程同时访问资源的数量,比如限制访问数据库连接池的线程数。
#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<1> semaphore(0);
void producer() {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "Producer is releasing semaphore." << std::endl;
semaphore.release();
}
void consumer() {
std::cout << "Consumer is waiting for semaphore." << std::endl;
semaphore.acquire();
std::cout << "Consumer acquired semaphore." << std::endl;
}
int main() {
std::thread p(producer);
std::thread c(consumer);
p.join();
c.join();
return 0;
}
#include <semaphore.h>
sem_t sem;
void *worker(void *arg) {
sem_wait(&sem); // 获取信号量
// 访问资源
sem_post(&sem); // 释放信号量
return NULL;
}
int main() {
sem_init(&sem, 0, 3); // 允许最多3个线程同时访问
return 0;
}