C++11中的mutex, lock, condition variable实现分析

栏目: C++ · 发布时间: 7年前

内容简介:C++11中的各种mutex, lock对象,实际上都是对posix的mutex,condition的封装。不过里面也有很多细节值得学习。

本文分析的是llvm libc++的实现:http://libcxx.llvm.org/

C++11中的各种mutex, lock对象,实际上都是对posix的mutex,condition的封装。不过里面也有很多细节值得学习。

std::mutex

先来看下std::mutex:

包增了一个pthread_mutex_t __m_,很简单,每个函数该干嘛就干嘛。

三种锁状态:std::defer_lock, std::try_to_lock, std::adopt_lock

这三个是用于标识锁在传递到一些包装类时,锁的状态:
std::defer_lock,还没有获取到锁
std::try_to_lock,在包装类构造时,尝试去获取锁
std::adopt_lock,调用者已经获得了锁
这三个东东,实际上是用于偏特化的,是三个空的struct:
在下面的代码里,就可以看到这三个东东是怎么用的了。

std::lock_guard

这个类比较重要,因为我们真正使用lock的时候,大部分都是要用这个。

这个类其实很简单:

在构造函数里调用 mutext.lock(),
在释构函数里,调用了mutex.unlock() 函数。

因为C++会在函数抛出异常时,自动调用作用域内的变量的析构函数,所以使用std::lock_guard可以在异常时自动释放锁,这就是为什么要避免直接使用mutex的函数,而是要用std::lock_guard的原因了。

注意,std::lock_guard的两个构造函数,当只传递mutex时,会在构造函数时调用mutext.lock()来获得锁。

当传递了adopt_lock_t时,说明调用者已经拿到了锁,所以不再尝试去获得锁。

std::unique_lock

unique_lock实际上也是一个包装类,起名为unique可能是和std::lock函数区分用的。
注意,多了一个owns_lock函数和release()函数,这两个在std::lock函数会用到。

owns_lock函数用于判断是否拥有锁;

release()函数则放弃了对锁的关联,当析构时,不会去unlock锁。
再看下unique_lock的实现,可以发现,上面的三种类型就是用来做偏特化用的

std::lock和std::try_lock函数

上面的都是类对象,这两个是函数。

std::lock和std::try_lock函数用于在同时使用多个锁时,防止死锁。这个实际上很重要的,因为手写代码来处理多个锁的同步问题,很容易出错。

要注意的是std::try_lock函数的返回值:

当成功时,返回-1;

当失败时,返回第几个锁没有获取成功,以0开始计数;

首先来看下只有两个锁的情况,代码虽然看起来比较简单,但里面却有大文章:

上面的lock函数用尝试的办法防止了死锁。

上面是两个锁的情况,那么在多个参数的情况下呢?

先来看下std::try_lock函数的实现:

里面递归地调用了try_lock函数自身,如果全部锁都获取成功,则依次把所有的unique_lock都release掉。

如果有失败,则计数失败的次数,最终返回。

再来看多参数的std::lock的实现:

可以看到多参数的std::lock的实现是:

先获取一个锁,然后再调用std::try_lock去获取剩下的锁,如果失败了,则下次先获取上次失败的锁。

重复上面的过程,直到成功获取到所有的锁。

上面的算法用比较巧妙的方式实现了参数的轮转。

std::timed_mutex

std::timed_mutex   是里面封装了mutex和condition,这样就两个函数可以用:
try_lock_for
try_lock_until

实际上是posix的mutex和condition的包装。

std::recursive_mutex和std::recursive_timed_mutex

这两个实际上是std::mutex和std::timed_mutex 的recursive模式的实现,即锁得获得者可以重复多次调用lock()函数。

和posix mutex里的recursive mutex是一样的。

看下std::recursive_mutex的构造函数就知道了。

std::cv_status

这个用来表示condition等待返回的状态的,和上面的三个表示lock的状态的用途差不多。

std::condition_variable

包装了posix condition variable。

里面的函数都是符合直觉的实现,值得注意的是:
cv_status是通过判断时间而确定的,如果超时的则返回cv_status::timeout,如果没有超时,则返回cv_status::no_timeout。

condition_variable::wait_until函数可以传入一个predicate,即一个用户自定义的判断是否符合条件的函数。这个也是很常见的模板编程的方法了。

std::condition_variable_any

std::condition_variable_any的接口和std::condition_variable一样,不同的是std::condition_variable只能使用std::unique_lock,而std::condition_variable_any可以使用任何的锁对象。

下面来看下为什么std::condition_variable_any可以使用任意的锁对象。

可以看到,在std::condition_variable_any里,用shared_ptr  __mut_来包装了mutex。所以一切都明白了,回顾std::unique_lock,它包装了mutex,当析构时自动释放mutex。在std::condition_variable_any里,这份工作让shared_ptr来做了。
因此,也可以很轻松得出std::condition_variable_any会比std::condition_variable稍慢的结论了

其它的东东:

sched_yield()函数的man手册:
sched_yield() causes the calling thread to relinquish the CPU.  The thread is moved to the end of the queue for its
static priority and a new thread gets to run.

在C++14里还有std::shared_lock和std::shared_timed_mutex,但是libc++里还没有对应的实现,因此不做分析。

总结

llvm libc++中的各种mutex, lock, condition variable实际上是封闭了posix里的对应实现。封装的技巧和一些细节值得细细推敲学习。

看完了实现源码之后,对于如何使用就更加清晰了。

参考:

http://en.cppreference.com/w/cpp

http://libcxx.llvm.org/


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big Java Late Objects

Big Java Late Objects

Horstmann, Cay S. / 2012-2 / 896.00元

The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具