《锁机制》

一、互斥锁

       1. mutext

std::mutex mtx;
mtx.lock();
mtx.unlock();

        对于mutex来说,在使用的时候,需要定义一个mutex对象,然后对mutext加锁、解锁。这样需要去管理加锁和解锁的时机,使用起来不是很好,所以可以采取RAII的方式,创建时加锁,析构时解锁。所以对mutex进行类封装,就有了lock_guard。

注意:当一个线程释放一个互斥锁时,会发生以下步骤

  1. 互斥锁被释放,此时锁处于可用状态。

  2. 操作系统的线程调度器会检查是否有其他线程正在等待获取这个锁。

  3. 如果有等待的线程,操作系统会从等待队列中选择一个线程,将其从阻塞状态切换到就绪状态。

  4. 被唤醒的线程会重新尝试获取这个互斥锁。如果成功获取,它就可以继续执行。如果仍然无法获取,它会再次进入阻塞状态。

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有三种:

  1. wait

  2. wait_for

  3. 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);  
    }  
};

四、读写锁

可以多个同时都读,但有写的时候只能一个:

  1. shared_mutex rwlock; 定义一个读写锁

  2. rwlock.lock_shared(); 锁读锁

  3. 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,就会出错。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值