可以看到多参数的std::lock的实现是:
先获取一个锁,然后再调用std::try_lock去获取剩下的锁,如果失败了,则下次先获取上次失败的锁。
重复上面的过程,直到成功获取到所有的锁。
上面的算法用比较巧妙的方式实现了参数的轮转。
std::timed_mutex
std::timed_mutex 是里面封装了mutex和condition,这样就两个函数可以用:
try_lock_for
try_lock_until
实际上是posix的mutex和condition的包装。
class timed_mutex { mutex __m_; condition_variable __cv_; bool __locked_; public: timed_mutex(); ~timed_mutex(); private: timed_mutex(const timed_mutex&); // = delete; timed_mutex& operator=(const timed_mutex&); // = delete; public: void lock(); bool try_lock() _NOEXCEPT; template _LIBCPP_INLINE_VISIBILITY bool try_lock_for(const chrono::duration& __d) {return try_lock_until(chrono::steady_clock::now() + __d);} template bool try_lock_until(const chrono::time_point& __t); void unlock() _NOEXCEPT; }; template bool timed_mutex::try_lock_until(const chrono::time_point& __t) { using namespace chrono; unique_lock __lk(__m_); bool no_timeout = _Clock::now() std::recursive_mutex和std::recursive_timed_mutex |
这两个实际上是std::mutex和std::timed_mutex 的recursive模式的实现,即锁得获得者可以重复多次调用lock()函数。
和posix mutex里的recursive mutex是一样的。
看下std::recursive_mutex的构造函数就知道了。
recursive_mutex::recursive_mutex() { pthread_mutexattr_t attr; int ec = pthread_mutexattr_init(&attr); if (ec) goto fail; ec = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); if (ec) { pthread_mutexattr_destroy(&attr); goto fail; } ec = pthread_mutex_init(&__m_, &attr); if (ec) { pthread_mutexattr_destroy(&attr); goto fail; } ec = pthread_mutexattr_destroy(&attr); if (ec) { pthread_mutex_destroy(&__m_); goto fail; } return; fail: __throw_system_error(ec, "recursive_mutex constructor failed"); } std::cv_status |
这个用来表示condition等待返回的状态的,和上面的三个表示lock的状态的用途差不多。
enum cv_status
{
no_timeout,
timeout
};
std::condition_variable
包装了posix condition variable。
class condition_variable { pthread_cond_t __cv_; public: condition_variable() {__cv_ = (pthread_cond_t)PTHREAD_COND_INITIALIZER;} ~condition_variable(); private: condition_variable(const condition_variable&); // = delete; condition_variable& operator=(const condition_variable&); // = delete; public: void notify_one() _NOEXCEPT; void notify_all() _NOEXCEPT; void wait(unique_lock& __lk) _NOEXCEPT; template void wait(unique_lock& __lk, _Predicate __pred); template cv_status wait_until(unique_lock& __lk, const chrono::time_point& __t); template bool wait_until(unique_lock& __lk, const chrono::time_point& __t, _Predicate __pred); template cv_status wait_for(unique_lock& __lk, const chrono::duration& __d); template bool wait_for(unique_lock& __lk, const chrono::duration& __d, _Predicate __pred); typedef pthread_cond_t* native_handle_type; _LIBCPP_INLINE_VISIBILITY native_handle_type native_handle() {return &__cv_;} private: void __do_timed_wait(unique_lock& __lk, chrono::time_point) _NOEXCEPT; }; |
里面的函数都是符合直觉的实现,值得注意的是:
cv_status是通过判断时间而确定的,如果超时的则返回cv_status::timeout,如果没有超时,则返回cv_status::no_timeout。
condition_variable::wait_until函数可以传入一个predicate,即一个用户自定义的判断是否符合条件的函数。这个也是很常见的模板编程的方法了。
template
cv_status
condition_variable::wait_until(unique_lock& __lk,
const chrono::time_point& __t)
{
using namespace chrono;
wait_for(__lk, __t - _Clock::now());
return _Clock::now()
bool
condition_variable::wait_until(unique_lock& __lk,
const chrono::time_point& __t,
_Predicate __pred)
{
while (!__pred())
{
if (wait_until(__lk, __t) == cv_status::timeout)
return __pred();
}
return true;
}
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可以使用任意的锁对象。
class _LIBCPP_TYPE_VIS condition_variable_any { condition_variable __cv_; shared_ptr __mut_; public: condition_variable_any(); void notify_one() _NOEXCEPT; void notify_all() _NOEXCEPT; template void wait(_Lock& __lock); template void wait(_Lock& __lock, _Predicate __pred); template cv_status wait_until(_Lock& __lock, const chrono::time_point& __t); template bool wait_until(_Lock& __lock, const chrono::time_point& __t, _Predicate __pred); template cv_status wait_for(_Lock& __lock, const chrono::duration& __d); template bool wait_for(_Lock& __lock, const chrono::duration& __d, _Predicate __pred); }; |
可以看到,在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里的对应实现。封装的技巧和一些细节值得细细推敲学习。
看完了实现源码之后,对于如何使用就更加清晰了。