我们在操作系统的相关知识中已经了解过了有关并发技术的基本知识,mutex 就是其中的核心之一。C++11引入了 mutex 相关的类,其所有相关的函数都放在 <mutex> 头文件中。
std::mutex 是 C++11 中最基本的 mutex 类,通过实例化 std::mutex 可以创建互斥量,而通过其成员函数 lock() 可以仅此能上锁,unlock() 可以进行解锁。但是在在实际编写代码的过程中,最好不去直接调用成员函数,因为调用成员函数就需要在每个临界区的出口处调用 unlock(),当然,还包括异常。这时候 C++11 还为互斥量提供了一个 RAII 语法的模板类std::lock_gurad。RAII 在不失代码简洁性的同时,很好的保证了代码的异常安全性。
在 RAII 用法下,对于临界区的互斥量的创建只需要在作用域的开始部分,例如:
由于 C++保证了所有栈对象在声明周期结束时会被销毁,所以这样的代码也是异常安全的。无论 some_operation() 正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 unlock()。
std::mutex 是 C++11 中最基本的 mutex 类,通过实例化 std::mutex 可以创建互斥量,而通过其成员函数 lock() 可以仅此能上锁,unlock() 可以进行解锁。但是在在实际编写代码的过程中,最好不去直接调用成员函数,因为调用成员函数就需要在每个临界区的出口处调用 unlock(),当然,还包括异常。这时候 C++11 还为互斥量提供了一个 RAII 语法的模板类std::lock_gurad。RAII 在不失代码简洁性的同时,很好的保证了代码的异常安全性。
在 RAII 用法下,对于临界区的互斥量的创建只需要在作用域的开始部分,例如:
void some_operation(const std::string &message) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
// ...操作
// 当离开这个作用域的时候,互斥锁会被析构,同时unlock互斥锁
// 因此这个函数内部的可以认为是临界区
}
由于 C++保证了所有栈对象在声明周期结束时会被销毁,所以这样的代码也是异常安全的。无论 some_operation() 正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 unlock()。
而 std::unique_lock 则相对于 std::lock_guard 出现的,std::unique_lock 更加灵活,std::unique_lock 的对象会以独占所有权(没有其他的 unique_lock 对象同时拥有某个 mutex 对象的所有权)的方式管理 mutex 对象上的上锁和解锁的操作。所以在并发编程中,推荐使用 std::unique_lock。例如:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void block_area() {
std::unique_lock<std::mutex> lock(mtx);
//...临界区
}
int main() {
std::thread thd1(block_area); thd1.join();
return 0;
}
std::lock_guard和std::unique_lock的简单对比
区域锁std::lock_guard使用起来比较简单,除了构造函数外没有其他成员函数,在整个区域都有效。
区域锁std::unique_lock 除了有std::lock_guard的功能外,还提供了更多的成员函数,相对来说更灵活。
std::unique_lock 的最有用的一组函数为:
locks the associated mutex (public member function) | |
tries to lock the associated mutex, returns if the mutex is not available (public member function) | |
attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration (public member function) | |
tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached (public member function) | |
unlocks the associated mutex |
通过上面的函数,可以通过lock/unlock可以比较灵活的控制锁的范围,减小锁的粒度。
通过try_lock_for/try_lock_until则可以控制加锁的等待时间,此时这种锁又被成为为乐观锁。