C+11のmutex、lock、condition variableは分析を実現します.
本論文で解析したのは、llvm libc++の実現である.http://libcxx.llvm.org/
C+11の各種mutex、lockオブジェクトは、実際にはposixのmtex、conditionのパッケージです.でも、中にはたくさんの細かいところがあります.勉強になります.
std:mutex
まず次のstdを見にきます.:mutex:
カバンが一つ増えました.mutex同前m_,簡単です.各関数は何をすればいいですか?
この3つは、いくつかのパッケージにロックがかかっている状態を示すためのものです.
std:defer_ロックはまだ取れていません.
std::try_to_ロックは、パッケージ構造の時にロックを取ってみます.
std::adopt_ロック、使用者はロックを獲得しました.
この三つのものは、実は偏特化に使われています.三つの空いているstructです.
std:lock_gard
このクラスが重要です.私たちが本当にロックを使う時、ほとんどはこれを使います.
このクラスは実は簡単です.
コンストラクタでmuttext.lock()を呼び出し、構文関数でmuttex.unlock()関数を呼び出しました.
C++は関数に異常があると、スコープ内の変数の解析関数を自動的に呼び出すので、std:lock_を使います.gurardは異常時に自動的にロックを解除することができます.これはなぜmutexの関数を直接使用しないでください.std:lock_グアードのせいです.
adopt_を伝えたらロックtの場合は、使用者がロックを持っていることを説明しますので、もうロックを獲得しようとしません.
std:unique_ロック
unique_ロックは実際には包装類で、uniqueと名付けられています.std:lock関数と区別して使います.注意して、もう一つownsが増えました.ロック関数とrelease()関数は、std:lock関数で使用されます.
ownsロックがあるかどうかを判定するためのロック関数.
release()関数はロックに対する関連を放棄して、構造を分析する時、unlockロックに行くことはできません.またunique_を見てくださいロックの実現によって、上の3つのタイプが偏特化用に使われていることが分かります.
std:lockとstd:try_ロック関数は複数のロックを同時に使用する場合、デッドロックを防止するために使用されます.これは実際に重要です.手書きコードで複数のロックの同期問題を処理していますので、間違えやすいです.
注意するのはstd::try_ロック関数の戻り値:
成功したら-1を返します.
失敗した場合、いくつかの鍵を返しましたが、成功しませんでした.0でカウントを開始します.
まず二つの鍵しかない場合、コードは簡単に見えるが、中には大きな文章があります.
先にstdを見にきます.:try_ロック関数の実装:
中は再帰的にtry_を呼び出した.ロック関数自体は、すべてのロックが成功すれば、順次unique(u)をすべて取得します.ロックは全部リリースされます.
失敗があれば、失敗の回数をカウントし、最終的に戻ってきます.
まずロックを取ってからstdを呼び出します.ロックは残りのロックを取りに行きます.失敗したら、次に失敗した鍵を取ります.
上のプロセスを繰り返して、すべてのロックを成功裏に取得します.
上のアルゴリズムは比較的巧みな方法でパラメータの回転を実現した.
std:timed_mutex
std:timed_mutex 内部にmutexとconditionが封入されています.このように二つの関数で使えます.try_ロックfor try(u)ロックuntil
実はposixのmutexとconditionの包装です.
この二つは実際はstd:mutexとstd:timed_mutexのrecursiveモードの実現、すなわちロックされた取得者は、ロック関数を複数回呼び出すことができる.
posix mtexのrecursive mtexと同じです.
次のstdを見てください.:recursive_mutexの構造関数は分かります.
これはconditionが戻ってくるのを待っている状態を表すもので、上の3つがロックの状態を表すものと同じです.
posix condition variableを包装しました.
cv_statusは時間を判断することで確定され、タイムアウトしたらcv_に戻ります.status:timeout、もしタイムアウトがないなら、cv_に戻ります.status::ノ_timeout
conditionvariable:wait_until関数は、1つのpredicateに入ることができます.すなわち、1人のユーザがカスタマイズした判定が条件に合致するかどうかの関数です.これもよくあるテンプレートプログラミングの方法です.
std::condition_variable_anyのインターフェースとstd:condition_variableと同じで、違いはstd:condition_variableはstdしか使えません:unique_ロックで、std:condition_variable_anyは任意のロックオブジェクトを使用することができます.
次はなぜstdを見ますか?variable_anyは任意のロックオブジェクトを使用することができます.
回顧std::unique_ロックは、分解時に自動的にmtexを放出するmutexを包装しています.std::condition_variable_anyの中で、この仕事はshared_を譲ります.ptrがやってきました.
そのため、簡単にstdを得ることができます.variable_any会比std::condition_variableはやや遅い結論になりました.
他のもの:
sched_yield()関数のマンマニュアル:sched_yield()causes the caling thread to relinquish the CPU. The thread is moved to the end of the queue for its static prorit and a new thread gets to run.
C+14にstdがあります.ロックとストップ:shared_timed_mutexですが、libc++にはまだ対応ができていませんので、分析はしません.
締め括りをつける
llvm libc++の各種muttex、lock、condition variableは実際にposix内の対応を閉じて実現しました.パッケージの技術と細部については細かく勉強する価値があります.
ソースの実现を见终わったら、どうやって使うかが分かります.
参考:
http://en.cppreference.com/w/cpp
http://libcxx.llvm.org/
C+11の各種mutex、lockオブジェクトは、実際にはposixのmtex、conditionのパッケージです.でも、中にはたくさんの細かいところがあります.勉強になります.
std:mutex
まず次のstdを見にきます.:mutex:
カバンが一つ増えました.mutex同前m_,簡単です.各関数は何をすればいいですか?
class mutex
{
pthread_mutex_t __m_;
public:
mutex() _NOEXCEPT {__m_ = (pthread_mutex_t)<strong>PTHREAD_MUTEX_INITIALIZER</strong>;}
~mutex();
private:
mutex(const mutex&);// = delete;
mutex& operator=(const mutex&);// = delete;
public:
void lock();
bool try_lock() _NOEXCEPT;
void unlock() _NOEXCEPT;
typedef pthread_mutex_t* native_handle_type;
_LIBCPP_INLINE_VISIBILITY native_handle_type native_handle() {return &__m_;}
};
mutex::~mutex()
{
pthread_mutex_destroy(&__m_);
}
void mutex::lock()
{
int ec = pthread_mutex_lock(&__m_);
if (ec)
__throw_system_error(ec, "mutex lock failed");
}
bool mutex::try_lock() _NOEXCEPT
{
return pthread_mutex_trylock(&__m_) == 0;
}
void mutex::unlock() _NOEXCEPT
{
int ec = pthread_mutex_unlock(&__m_);
(void)ec;
assert(ec == 0);
}
三種類のロック状態:std:defer_ロック、std:try_to_ロック、std:adopt_ロックこの3つは、いくつかのパッケージにロックがかかっている状態を示すためのものです.
std:defer_ロックはまだ取れていません.
std::try_to_ロックは、パッケージ構造の時にロックを取ってみます.
std::adopt_ロック、使用者はロックを獲得しました.
この三つのものは、実は偏特化に使われています.三つの空いているstructです.
struct defer_lock_t {};
struct try_to_lock_t {};
struct adopt_lock_t {};
constexpr defer_lock_t defer_lock = defer_lock_t();
constexpr try_to_lock_t try_to_lock = try_to_lock_t();
constexpr adopt_lock_t adopt_lock = adopt_lock_t();
下のコードの中で、この三つのものはどうやって使うかが見えます.std:lock_gard
このクラスが重要です.私たちが本当にロックを使う時、ほとんどはこれを使います.
このクラスは実は簡単です.
コンストラクタでmuttext.lock()を呼び出し、構文関数でmuttex.unlock()関数を呼び出しました.
C++は関数に異常があると、スコープ内の変数の解析関数を自動的に呼び出すので、std:lock_を使います.gurardは異常時に自動的にロックを解除することができます.これはなぜmutexの関数を直接使用しないでください.std:lock_グアードのせいです.
template <class _Mutex>
class lock_guard
{
public:
typedef _Mutex mutex_type;
private:
mutex_type& __m_;
public:
explicit lock_guard(mutex_type& __m)
: __m_(__m) {__m_.lock();}
lock_guard(mutex_type& __m, adopt_lock_t)
: __m_(__m) {}
~lock_guard() {__m_.unlock();}
private:
lock_guard(lock_guard const&);// = delete;
lock_guard& operator=(lock_guard const&);// = delete;
};
注意、std::lock_Gurardの二つのコンストラクタは、muttexのみを伝達するとコンストラクタのときにmut.lock()を呼び出してロックを得る.adopt_を伝えたらロックtの場合は、使用者がロックを持っていることを説明しますので、もうロックを獲得しようとしません.
std:unique_ロック
unique_ロックは実際には包装類で、uniqueと名付けられています.std:lock関数と区別して使います.注意して、もう一つownsが増えました.ロック関数とrelease()関数は、std:lock関数で使用されます.
ownsロックがあるかどうかを判定するためのロック関数.
release()関数はロックに対する関連を放棄して、構造を分析する時、unlockロックに行くことはできません.またunique_を見てくださいロックの実現によって、上の3つのタイプが偏特化用に使われていることが分かります.
template <class _Mutex>
class unique_lock
{
public:
typedef _Mutex mutex_type;
private:
mutex_type* __m_;
bool __owns_;
public:
unique_lock() _NOEXCEPT : __m_(nullptr), __owns_(false) {}
explicit unique_lock(mutex_type& __m)
: __m_(&__m), __owns_(true) {__m_->lock();}
unique_lock(mutex_type& __m, defer_lock_t) _NOEXCEPT
: __m_(&__m), __owns_(false) {}
unique_lock(mutex_type& __m, try_to_lock_t) //
: __m_(&__m), __owns_(__m.try_lock()) {}
unique_lock(mutex_type& __m, adopt_lock_t) //
: __m_(&__m), __owns_(true) {}
template <class _Clock, class _Duration>
unique_lock(mutex_type& __m, const chrono::time_point<_Clock, _Duration>& __t)
: __m_(&__m), __owns_(__m.try_lock_until(__t)) {}
template <class _Rep, class _Period>
unique_lock(mutex_type& __m, const chrono::duration<_Rep, _Period>& __d)
: __m_(&__m), __owns_(__m.try_lock_for(__d)) {}
~unique_lock()
{
if (__owns_)
__m_->unlock();
}
private:
unique_lock(unique_lock const&); // = delete;
unique_lock& operator=(unique_lock const&); // = delete;
public:
unique_lock(unique_lock&& __u) _NOEXCEPT
: __m_(__u.__m_), __owns_(__u.__owns_)
{__u.__m_ = nullptr; __u.__owns_ = false;}
unique_lock& operator=(unique_lock&& __u) _NOEXCEPT
{
if (__owns_)
__m_->unlock();
__m_ = __u.__m_;
__owns_ = __u.__owns_;
__u.__m_ = nullptr;
__u.__owns_ = false;
return *this;
}
void lock();
bool try_lock();
template <class _Rep, class _Period>
bool try_lock_for(const chrono::duration<_Rep, _Period>& __d);
template <class _Clock, class _Duration>
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __t);
void unlock();
void swap(unique_lock& __u) _NOEXCEPT
{
_VSTD::swap(__m_, __u.__m_);
_VSTD::swap(__owns_, __u.__owns_);
}
mutex_type* release() _NOEXCEPT
{
mutex_type* __m = __m_;
__m_ = nullptr;
__owns_ = false;
return __m;
}
bool owns_lock() const _NOEXCEPT {return __owns_;}
operator bool () const _NOEXCEPT {return __owns_;}
mutex_type* mutex() const _NOEXCEPT {return __m_;}
};
std:lockとstd:try_ロック関数の上にあるのはすべてクラスのオブジェクトです.この二つは関数です.std:lockとstd:try_ロック関数は複数のロックを同時に使用する場合、デッドロックを防止するために使用されます.これは実際に重要です.手書きコードで複数のロックの同期問題を処理していますので、間違えやすいです.
注意するのはstd::try_ロック関数の戻り値:
成功したら-1を返します.
失敗した場合、いくつかの鍵を返しましたが、成功しませんでした.0でカウントを開始します.
まず二つの鍵しかない場合、コードは簡単に見えるが、中には大きな文章があります.
template <class _L0, class _L1>
void
lock(_L0& __l0, _L1& __l1)
{
while (true)
{
{
unique_lock<_L0> __u0(__l0);
if (__l1.try_lock()) // l0, l1
{
__u0.release(); //l0 l1 , unique_lock l0, release() , l0 。
break;
}
}// l0,l1 , l0。
sched_yield(); // ,CPU
{
unique_lock<_L1> __u1(__l1); // l1 , l1, l1( , )
if (__l0.try_lock())
{
__u1.release();
break;
}
}
sched_yield();
}
}
template <class _L0, class _L1>
int
try_lock(_L0& __l0, _L1& __l1)
{
unique_lock<_L0> __u0(__l0, try_to_lock);
if (__u0.owns_lock())
{
if (__l1.try_lock()) // try_lock ,
{
__u0.release();
return -1;
}
else
return 1;
}
return 0;
}
上のロック関数は試しにデッドロックを防止しました.上は2つのロックの場合、複数のパラメータの場合は?先にstdを見にきます.:try_ロック関数の実装:
中は再帰的にtry_を呼び出した.ロック関数自体は、すべてのロックが成功すれば、順次unique(u)をすべて取得します.ロックは全部リリースされます.
失敗があれば、失敗の回数をカウントし、最終的に戻ってきます.
template <class _L0, class _L1, class _L2, class... _L3>
int
try_lock(_L0& __l0, _L1& __l1, _L2& __l2, _L3&... __l3)
{
int __r = 0;
unique_lock<_L0> __u0(__l0, try_to_lock);
if (__u0.owns_lock())
{
__r = try_lock(__l1, __l2, __l3...);
if (__r == -1)
__u0.release();
else
++__r;
}
return __r;
}
マルチパラメータのstd:ロックの実装:template <class _L0, class _L1, class _L2, class ..._L3>
void
__lock_first(int __i, _L0& __l0, _L1& __l1, _L2& __l2, _L3& ...__l3)
{
while (true)
{
switch (__i) //__i , 0
{
case 0: // ,__i 0
{
unique_lock<_L0> __u0(__l0);
__i = try_lock(__l1, __l2, __l3...);
if (__i == -1) // l0 , , , unique_lock release,
{
__u0.release();
return;
}
}
++__i; // __i , try_lock(__l1,__l2__l3,...) l1 , +1, , 。
sched_yield();
break;
case 1: // l1 , l1。
{
unique_lock<_L1> __u1(__l1);
__i = try_lock(__l2, __l3..., __l0); // l0 。 l1, 。
if (__i == -1)
{
__u1.release();
return;
}
}
if (__i == sizeof...(_L3) + 1) // l0 , l0 。 l0, l0 。
__i = 0;
else
__i += 2; // __i , try_lock(__l2,__l3..., __l0) l2 , +2
sched_yield();
break;
default:
__lock_first(__i - 2, __l2, __l3..., __l0, __l1); // l2 , __i 2。
return;
}
}
}
template <class _L0, class _L1, class _L2, class ..._L3>
inline _LIBCPP_INLINE_VISIBILITY
void
lock(_L0& __l0, _L1& __l1, _L2& __l2, _L3& ...__l3)
{
__lock_first(0, __l0, __l1, __l2, __l3...);
}
マルチパラメータのstdが見られます.ロックの実現は以下の通りです.まずロックを取ってからstdを呼び出します.ロックは残りのロックを取りに行きます.失敗したら、次に失敗した鍵を取ります.
上のプロセスを繰り返して、すべてのロックを成功裏に取得します.
上のアルゴリズムは比較的巧みな方法でパラメータの回転を実現した.
std:timed_mutex
std:timed_mutex 内部にmutexとconditionが封入されています.このように二つの関数で使えます.try_ロックfor try(u)ロック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 <class _Rep, class _Period>
_LIBCPP_INLINE_VISIBILITY
bool try_lock_for(const chrono::duration<_Rep, _Period>& __d)
{return try_lock_until(chrono::steady_clock::now() + __d);}
template <class _Clock, class _Duration>
bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __t);
void unlock() _NOEXCEPT;
};
template <class _Clock, class _Duration>
bool
timed_mutex::try_lock_until(const chrono::time_point<_Clock, _Duration>& __t)
{
using namespace chrono;
unique_lock<mutex> __lk(__m_);
bool no_timeout = _Clock::now() < __t;
while (no_timeout && __locked_)
no_timeout = __cv_.wait_until(__lk, __t) == cv_status::no_timeout;
if (!__locked_)
{
__locked_ = true;
return true;
}
return false;
}
std:recursive_muttexとstd:recursive_timed_mutexこの二つは実際はstd:mutexとstd:timed_mutexのrecursiveモードの実現、すなわちロックされた取得者は、ロック関数を複数回呼び出すことができる.
posix mtexのrecursive mtexと同じです.
次の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が戻ってくるのを待っている状態を表すもので、上の3つがロックの状態を表すものと同じです.
enum cv_status
{
no_timeout,
timeout
};
std::condition_variableposix 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<mutex>& __lk) _NOEXCEPT;
template <class _Predicate>
void wait(unique_lock<mutex>& __lk, _Predicate __pred);
template <class _Clock, class _Duration>
cv_status
wait_until(unique_lock<mutex>& __lk,
const chrono::time_point<_Clock, _Duration>& __t);
template <class _Clock, class _Duration, class _Predicate>
bool
wait_until(unique_lock<mutex>& __lk,
const chrono::time_point<_Clock, _Duration>& __t,
_Predicate __pred);
template <class _Rep, class _Period>
cv_status
wait_for(unique_lock<mutex>& __lk,
const chrono::duration<_Rep, _Period>& __d);
template <class _Rep, class _Period, class _Predicate>
bool
wait_for(unique_lock<mutex>& __lk,
const chrono::duration<_Rep, _Period>& __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<mutex>& __lk,
chrono::time_point<chrono::system_clock, chrono::nanoseconds>) _NOEXCEPT;
};
中の関数は直感的に実現されています.注意すべきことは:cv_statusは時間を判断することで確定され、タイムアウトしたらcv_に戻ります.status:timeout、もしタイムアウトがないなら、cv_に戻ります.status::ノ_timeout
conditionvariable:wait_until関数は、1つのpredicateに入ることができます.すなわち、1人のユーザがカスタマイズした判定が条件に合致するかどうかの関数です.これもよくあるテンプレートプログラミングの方法です.
template <class _Clock, class _Duration>
cv_status
condition_variable::wait_until(unique_lock<mutex>& __lk,
const chrono::time_point<_Clock, _Duration>& __t)
{
using namespace chrono;
wait_for(__lk, __t - _Clock::now());
return _Clock::now() < __t ? cv_status::no_timeout : cv_status::timeout;
}
template <class _Clock, class _Duration, class _Predicate>
bool
condition_variable::wait_until(unique_lock<mutex>& __lk,
const chrono::time_point<_Clock, _Duration>& __t,
_Predicate __pred)
{
while (!__pred())
{
if (wait_until(__lk, __t) == cv_status::timeout)
return __pred();
}
return true;
}
std::condition_variable_anystd::condition_variable_anyのインターフェースとstd:condition_variableと同じで、違いはstd:condition_variableはstdしか使えません:unique_ロック
次はなぜstdを見ますか?variable_anyは任意のロックオブジェクトを使用することができます.
class _LIBCPP_TYPE_VIS condition_variable_any
{
condition_variable __cv_;
shared_ptr<mutex> __mut_;
public:
condition_variable_any();
void notify_one() _NOEXCEPT;
void notify_all() _NOEXCEPT;
template <class _Lock>
void wait(_Lock& __lock);
template <class _Lock, class _Predicate>
void wait(_Lock& __lock, _Predicate __pred);
template <class _Lock, class _Clock, class _Duration>
cv_status
wait_until(_Lock& __lock,
const chrono::time_point<_Clock, _Duration>& __t);
template <class _Lock, class _Clock, class _Duration, class _Predicate>
bool
wait_until(_Lock& __lock,
const chrono::time_point<_Clock, _Duration>& __t,
_Predicate __pred);
template <class _Lock, class _Rep, class _Period>
cv_status
wait_for(_Lock& __lock,
const chrono::duration<_Rep, _Period>& __d);
template <class _Lock, class _Rep, class _Period, class _Predicate>
bool
wait_for(_Lock& __lock,
const chrono::duration<_Rep, _Period>& __d,
_Predicate __pred);
};
はstdで見られます.variable_anyでは、shared(u)を使いますptr<mutex> __ムント.mutexを包装しに来ました.ですから、すべて分かりました.回顧std::unique_ロック
そのため、簡単にstdを得ることができます.variable_any会比std::condition_variableはやや遅い結論になりました.
他のもの:
sched_yield()関数のマンマニュアル:sched_yield()causes the caling thread to relinquish the CPU. The thread is moved to the end of the queue for its static prorit and a new thread gets to run.
C+14にstdがあります.ロックとストップ:shared_timed_mutexですが、libc++にはまだ対応ができていませんので、分析はしません.
締め括りをつける
llvm libc++の各種muttex、lock、condition variableは実際にposix内の対応を閉じて実現しました.パッケージの技術と細部については細かく勉強する価値があります.
ソースの実现を见终わったら、どうやって使うかが分かります.
参考:
http://en.cppreference.com/w/cpp
http://libcxx.llvm.org/