Skip to content

或许可以大致分为三个部分 ——

  • Mutex 类型
  • RAII 锁管理器
  • 使用策略和算法

最简单的例子 ——

C++
std::mutex mutex;

void Foo() {
	std::lock_guard lock(mutex);
	
	// 临界区资源保护
}

Mutex 类型

通常来说,会提供 ——

  • mutex.lock();
  • mutex.unlock();
  • mutex.try_lock();

这三个接口,然后针对不同的 Mutex 类型会有更多其他功能

不过最基础的互斥量还是std::mutex

简单例子 ——

C++
class Counter { 
  public:
	void Increment() {
		std::lock_guard lock(mutex_);
		++value_;
	}
	
	int Get() const {
		std::lock_guard lock(mutex_);
		return value_;
	}
	
  private:
	mutable std::mutex mutex_;
	int value_{0};
};

(此处使用 mutable 是因为在使用的时候会改变内部状态,哪怕在Get方法中使用了const修饰

timed_mutex

支持超时等待的互斥量

额外提供了 ——

  • try_lock_for():等待一段时间
  • try_lock_until():等待到某个时间点

这两个方法

首先是try_lock_for(),阻塞当前线程等待一段时间,如果这一段时间内锁是可lock的,则返回true,否则返回false

C++
bool TryUseResource() {
	if (!mutex.try_lock_for( std::chrono::milliseconds(100) ) ) {
		return false;
	}
	UseResource();
	mutex.unlock();
	return true;
}

当然,这样如果在 UseResource() 的时候抛出异常,则会导致锁无法正常解锁,下面提供一个RAII封装后的写法(虽然这部分放在后面说明逻辑性可能会更强)

C++
bool TryUseResource() {
	std::unique_lock lock( mutex, std::defer_lock);
	if (!lock.try_lock_for( std::chrono::milliseconds(100))) {
		return false;
	}
	UseResource();
	return true;
}

try_lock_until() 同理,只是变成了阻塞等待到指定的时间点

shared_mutex

std::shared_mutex 是 Reader/Writer Lock,即读写锁

它有两种不同的获取模式 ——

  • 独占锁:Writer 使用
  • 共享锁:Reader 使用

lock_shared() 方法的语义为等待获得共享读取权 ——

  • 如果当前没有 Writer 持有独占锁: 当前线程获得共享锁
  • 如果存在 Writer: 当前线程阻塞等待

对应的是unlock_shared()

不过依旧推荐使用RAII管理器去进行锁的资源管理

RAII 锁管理器

用于管理锁,比如在手动mutex.lock()之后,临界区代码抛出异常则会导致没有unlock()的操作,那很坏了,所以使用RAII包装一下,以便在合适的时刻执行lockunlock

主要包括:

  • lock_guard
  • unique_lock
  • scoped_lock
  • shared_lock

lock_guard

最基础的锁管理器,在构造的时候触发.lock(),在析构的时候触发.unlock()

适合最普通的单锁临界区,比如 ——

C++
void Increment() {
    std::lock_guard lock(mutex_);
    ++value_;
}

unique_lock

更灵活的独占锁管理器 —— 可以比较自由的决定关联和持有锁的触发时机,同时也支持所有权的转移(完整的移动语义)

延迟加锁

C++
std::unique_lock lock(mutex, std::defer_lock);

此时只是关联 Mutex,并没有获得锁

可以在后续手动获得锁(比如lock或者try_lock甚至是try_lock_for,如果关联的锁有定义的话)

同理,也可以在初始化的时候生命非阻塞的尝试获取锁

C++
std::unique_lock lock(mutex, std::try_to_lock);

等价于

C++
std::unique_lock lock(mutex, std::defer_lock);

const bool success = lock.try_lock();

超时等待

如果关联的锁支持

  • try_lock_for();
  • try_lock_until();

也可以调用相关接口

比如

C++
std::unique_lock lock(mutex, std::defer_lock);

if (!lock.try_lock_for(
        std::chrono::milliseconds(100))) {
    return false;
}

条件变量

由于条件变量在 wait 的时候必须暂时接管当前线程对 Mutex 的所有权,在等待期间释放它,并在返回前重新获得它,而unique_lock 正是用来表示和管理这种会发生变化的锁所有权,所以才必须传入unique_lock

(挖坑,蹲一个专门写条件变量的 note)

提供一个最基础的例子 ——

C++
std::unique_lock lock(mutex_);
condition_.wait(lock, [this] { return ready_; });

scoped_lock

主要用于多锁的情况,它会以死锁规避方式获得所有锁。语义上的理解为 —— 所有锁作为一个整体,在整个作用域内持有

所以它没有unlock方法(如果需要手动进行释放的话,或许使用unique_lock + lock 是一种更好的选择)

值得注意的,此处不能传入两个一样的锁,不然会破坏底层的死锁规避方法

shared_lock

用于管理共享锁,通常搭配 ——

  • shared_mutex
  • shared_timed_mutex

特点与 unique_lock 类似,但调用的是共享接口

Released under the MIT License.