[cpp] cpp11--锁

目录

1. mutex

2. timed_mutex

3. recursive_mutex

4. lock_guard && unique_lock


1. mutex

下面我们来看一下互斥锁. mutex的主要接口是:

  • lock: 如果有其他线程抢到锁了, 那么这个线程就处在阻塞状态
  • try_lock: 如果有其他人抢到锁了, 那么我这个线程就不阻塞了, 返回 false, 更加灵活一些.

2. timed_mutex

除了 mutex 相关的接口, cpp11 当中还支持一个功能更加丰富的 timed_mutex(定时互斥锁 ), 下面来看看其相关的接口:

相比于一般的 mutex(互斥锁), 这个锁的话是有一个"定时"的功能的.

  • try_lock_for: 加入一个最大的时间限定, for 代表一个时间段, 比如 200ms.
  • try_lock_until: 这个的话就是最多锁到哪个时间点. until 代表的是时间点的概念.

下面展示一个 until 的一个具体用法示例:

// timed_mutex::try_lock_until example
#include <iostream>       // std::cout
#include <chrono>         // std::chrono::system_clock
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex
#include <ctime>          // std::time_t, std::tm, std::localtime, std::mktime

std::timed_mutex cinderella;

// gets time_point for next midnight:
std::chrono::time_point<std::chrono::system_clock> midnight() {
  using std::chrono::system_clock;
  std::time_t tt = system_clock::to_time_t (system_clock::now());
  struct std::tm * ptm = std::localtime(&tt);
  ++ptm->tm_mday; ptm->tm_hour=0; ptm->tm_min=0; ptm->tm_sec=0;
  return system_clock::from_time_t (mktime(ptm));
}

void carriage() {
  if (cinderella.try_lock_until(midnight())) {
    std::cout << "ride back home on carriage\n";
    cinderella.unlock();
  }
  else
    std::cout << "carriage reverts to pumpkin\n";
}

void ball() {
  cinderella.lock();
  std::cout << "at the ball...\n";
  cinderella.unlock();
}

int main ()
{
  std::thread th1 (ball);
  std::thread th2 (carriage);

  th1.join();
  th2.join();

  return 0;
}

这个代码啥意思呢? 就是两个线程 th1 和 th2 在互斥的运行打印... th1 执行void ball(), 然后 th2 执行void carriage(), 打印的时候都是加了锁了, 这样可以避免打印到显示器上的信息错乱.

不过有意思的是, th2 在执行carriage的时候, 他最多竞争锁到第二天的零时零分零秒, 此后就不再参与竞争锁了, 之后灰姑娘的马车就变成了南瓜...

carriage 函数模拟了灰姑娘乘坐马车回家的场景。它会尝试获取一个互斥锁,这个锁代表了马车的使用权限。获取锁的尝试会持续到下一个午夜(即第二天的 00:00:00)。如果在这个时间之前成功获取到锁,就意味着灰姑娘可以乘坐马车回家;如果到了午夜还没有获取到锁,那么马车就会变回南瓜。

3. recursive_mutex

还有一个递归锁recursive_mutex. 因为互斥锁有个小问题, 递归自己可能会吧自己吧自己死锁掉.
这个递归锁呢就是lock()的时候做了一个事情, 第一次锁就直接锁, 如果同一个线程再次过来锁就继续锁, 不会出现死锁情况.

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

// 递归函数,尝试多次加锁
void recursiveFunction(int n) {
    // 尝试加锁
    mtx.lock();
    std::cout << "Acquired lock at depth: " << n << std::endl;
    if (n > 0) {
        // 递归调用自身
        recursiveFunction(n - 1);
    }
    // 尝试解锁
    mtx.unlock();
    std::cout << "Released lock at depth: " << n << std::endl;
}

int main() {
    // 调用递归函数
    recursiveFunction(3);

    return 0;
}

当第一次调用 recursiveFunction 时,线程成功获取了锁。但在递归调用 recursiveFunction 时,线程再次尝试获取同一个锁,由于 std::mutex 不允许同一个线程多次获取同一把锁,所以线程会被阻塞,等待锁被释放。然而,锁的释放操作需要在递归调用返回后才能执行,这就导致线程自己等待自己释放锁,从而造成死锁。

实际上,同一个线程再次对 std::recursive_mutex 加锁时,依然会成功加锁,并非 “不锁”。std::recursive_mutex 会记录加锁的次数,每次调用 lock() 方法时,加锁次数就会增加;每次调用 unlock() 方法时,加锁次数就会减少。只有当加锁次数降为 0 时,锁才会真正被释放,其他线程才能获取该锁。

#include <iostream>
#include <mutex>
#include <thread>

std::recursive_mutex recursive_mtx;

void recursiveFunction(int n) {
    recursive_mtx.lock();
    std::cout << "Lock acquired. Depth: " << n << std::endl;
    if (n > 0) {
        recursiveFunction(n - 1);
    }
    std::cout << "Unlocking at depth: " << n << std::endl;
    recursive_mtx.unlock();
}

int main() {
    std::thread t(recursiveFunction, 3);
    t.join();
    return 0;
}

4. lock_guard && unique_lock

平时我们的lock和unlock()有个风险, 就是中间临界代码如果抛异常了, 那么就不会解锁了, 从而导致一个死锁(因为没解锁)的情况.

所以为了解决这个问题, 有人就设计了一个lockGuard的一个类, 我们之前unlock的问题在于抛异常了就不会执行unlock了, 也就是不会还锁了, 不会还锁那谁都进不来了. 也就是抛异常改变了线程要还锁的一个举动.

这个 lock_guard 是封装引用了 mutex, 算是一个封装了一个类对象的锁, 这玩意很巧妙的结合了域 + 构造 + 析构的用法解决了抛出异常死锁的问题. 这样就可以很巧妙的避免这个尴尬情况, 因为我们明白在cpp语法当中, 类对象是自动销毁的, 自动销毁也就意味着会自动调用析构函数, 而这个lockGuard就是通过析构自动还锁. 程序在临界区抛异常了, 没关系, 这个锁的对象就直接调用析构还锁就好了.

我们下面看一下lock_guard一个极简的模拟实现:

#include <iostream>
#include <mutex>
#include <thread>
using namespace std;
class LockGuard
{
public:
	LockGuard(mutex& mtx) :_mtx(mtx)
	{
		_mtx.lock();
	}
	~LockGuard()
	{
		_mtx.unlock();
	}

private:
	mutex& _mtx;
};

那有些同学说, 我加入说只想在 for 循环所在的这个局部域里去加锁一部分代码呢? 可以吗? 可以, 加个局部作用域即可.

库中的lockGuard不仅仅局限在metex, 库里面用了一个模板, 还可以适配其他的锁, 而不仅仅局限于互斥锁.

另外一个 -> unique_lock
跟lockGuard的区别在于, 支持手动加锁和解锁, 算是一个更加可以自定义的锁守卫~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值