一、互斥锁
1. mutext
std::mutex mtx;
mtx.lock();
mtx.unlock();
对于mutex来说,在使用的时候,需要定义一个mutex对象,然后对mutext加锁、解锁。这样需要去管理加锁和解锁的时机,使用起来不是很好,所以可以采取RAII的方式,创建时加锁,析构时解锁。所以对mutex进行类封装,就有了lock_guard。
注意:当一个线程释放一个互斥锁时,会发生以下步骤
-
互斥锁被释放,此时锁处于可用状态。
-
操作系统的线程调度器会检查是否有其他线程正在等待获取这个锁。
-
如果有等待的线程,操作系统会从等待队列中选择一个线程,将其从阻塞状态切换到就绪状态。
-
被唤醒的线程会重新尝试获取这个互斥锁。如果成功获取,它就可以继续执行。如果仍然无法获取,它会再次进入阻塞状态。
2.lock_guard
private:
_Mutex& _MyMutex;
public:
//构造函数
explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
_MyMutex.lock(); //构造是加锁,注意需要传入一个mutex
}
//析构函数
~lock_guard() noexcept {
_MyMutex.unlock(); //析构时解锁
}
lock_guard的问题是,加锁/解锁的时机是固定的,使用起来不灵活。
3.unique_lock
private:
_Mutex* _Pmtx = nullptr;
bool _Owns = false;
public:
//构造函数
_NODISCARD_CTOR_LOCK explicit unique_lock(_Mutex& _Mtx)
: _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock
_Pmtx->lock();
_Owns = true;
}
//析构函数
~unique_lock() noexcept {
if (_Owns) {
_Pmtx->unlock();
}
}
//lock
void lock() { // lock the mutex
_Validate();
_Pmtx->lock();
_Owns = true;
}
//trylock
_NODISCARD_TRY_CHANGE_STATE bool try_lock() {
_Validate();
_Owns = _Pmtx->try_lock();
return _Owns;
}
//unlock
void unlock() {
if (!_Pmtx || !_Owns) {
_Throw_system_error(errc::operation_not_permitted);
}
_Pmtx->unlock();
_Owns = false;
}
二、条件锁
条件变量配合互斥锁
std::condition_variable cond; //条件变量
std::mutex mxt; //互斥锁
//生产者
void producer() {
while (true) {
std::unique_lock<std::mutex> locker(mxt);
cond.wait(locker, [&]()->bool {return q.size() < MAXSIZE; });
q.push_back(1);
cond.notify_all();
std::cout << "producer push" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
//消费者
void consumer() {
while (true) {
std::unique_lock<std::mutex> locker(mxt);
cond.wait(locker, [&]()->bool {return q.size() > 0; });
q.pop_front();
cond.notify_all();
std::cout << "consumer pop" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
wait有三种:
-
wait
-
wait_for
-
wait_until
>>>>wait<<<<
wait(unique_lock<mutex>& lock) //需要传入锁,因为条件不满足需要解锁,正是因为此所以不能用lock_guard
wait(unique_lock<mutex>& lock, Predicate pred) //Predicate pred是条件
>>>>wait_for<<<<
wait_for(unique_lock<mutex>& lock, Rep rep, Period period) //设置等待时间
wait_for(unique_lock<mutex>& lock, Rep rep, Period period, Predicate pred)
//例如cond.wait_for(locker, std::chrono::seconds(1), [&]()->bool {return q.size() > 0; });
>>>>wait_until<<<<
wait_until(unique_lock<mutex>& lock, TimePoint abs_time) //设置要等待时间
wait_until(unique_lock<mutex>& lock, TimePoint abs_time, Predicate pred)
//cond.wait_until(locker, std::chrono::system_clock::now() + std::chrono::seconds(1), [&]()->bool {return q.size() > 0; });
总结:可以看出,条件锁调用wait的时候,会进入阻塞状态等待条件成立,当条件成立的时候,需要再次进行加锁,加速成功就可以运行了。因为阻塞的方式,可以不一直占用CPU资源。
三、自旋锁
自旋锁就不同了,自旋锁是一种 busy-waiting
的锁。也就是说,如果T1
正在使用自旋锁,而T2
也去申请这个自旋锁,此时T2
肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2
的处理器core2
会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。
自旋锁需要使用到原子操作:
class spin_lock {
private:
std::atomic_flag flag = { ATOMIC_FLAG_INIT };
public:
spin_lock() {}
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
// spin-wait loop
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
四、读写锁
可以多个同时都读,但有写的时候只能一个:
-
shared_mutex rwlock; 定义一个读写锁
-
rwlock.lock_shared(); 锁读锁
-
rwlock.lock(); 锁写锁
std::shared_mutex rwlock;
int sharedData = 0;
void readerThread() {
while (true) {
// 读取共享数据
rwlock.lock_shared();
std::cout << "Reader thread ID: " << std::this_thread::get_id() << ", Shared data: " << sharedData << std::endl;
rwlock.unlock_shared();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void writerThread() {
while (true) {
// 修改共享数据
rwlock.lock();
sharedData++;
std::cout << "Writer thread ID: " << std::this_thread::get_id() << ", Updated shared data: " << sharedData << std::endl;
rwlock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
五、递归锁
递归锁解决的问题是,对同一把锁重复加锁,来获得对互斥量对象的多层所有权,std::recursive_mutex
释放互斥量时需要调用与该锁层次深度相同次数的 unlock()
,可理解为 lock()
次数和 unlock()
次数相同,除此之外,std::recursive_mutex
的特性和 std::mutex
大致相同。
例如函数 a
需要获取锁 mutex
,函数 b
也需要获取锁 mutex
,同时函数 a
中还会调用函数 b
。如果使用std::mutex
必然会造成死锁。但是使用 std::recursive_mutex
就可以解决这个问题。
//使用递归锁就可以解决一个函数调用另一个函数,对同一把锁加锁的问题
std::recursive_mutex rmux;
void funcSeconde() {
rmux.lock();
std::cout << "seconde layer lock" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
rmux.unlock();
}
void funcFirst() {
while (true)
{
rmux.lock();
std::cout << "fist layer lock" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
//do
funcSeconde();
rmux.unlock();
}
}
如果使用mutex,就会出错。