From: Jonathan Wakely Date: Fri, 10 Apr 2015 08:59:54 +0000 (+0100) Subject: shared_mutex (shared_timed_mutex): Add comments to explain the logic in the non-pthre... X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=43b0e124e455ebabe9df6bb4d9959beb9eb9b633;p=gcc.git shared_mutex (shared_timed_mutex): Add comments to explain the logic in the non-pthread_rwlock_t version. * include/std/shared_mutex (shared_timed_mutex): Add comments to explain the logic in the non-pthread_rwlock_t version. (_Mutex): Remove redundant type. (_M_n_readers): Rename to _S_max_readers. (_M_write_entered, _M_readers): New convenience functions. (lock, lock_shared, try_lock_shared, unlock_shared): Use convenience functions. Use predicates with condition variables. Simplify bitwise operations. (try_lock_for, try_shared_lock_for): Convert duration to time_point and call try_lock_until or try_shared_lock_until respectively. (try_lock_until, try_shared_lock_until): Wait on the condition variables until the specified time passes. (unlock): Add Debug Mode assertion. (unlock_shared): Add Debug Mode assertion. * testsuite/30_threads/shared_timed_mutex/try_lock/3.cc: New. From-SVN: r221970 --- diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 698de1b6b06..e4ca68dee85 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,21 @@ +2015-04-10 Jonathan Wakely + + * include/std/shared_mutex (shared_timed_mutex): Add comments to + explain the logic in the non-pthread_rwlock_t version. + (_Mutex): Remove redundant type. + (_M_n_readers): Rename to _S_max_readers. + (_M_write_entered, _M_readers): New convenience functions. + (lock, lock_shared, try_lock_shared, unlock_shared): Use convenience + functions. Use predicates with condition variables. Simplify bitwise + operations. + (try_lock_for, try_shared_lock_for): Convert duration to time_point + and call try_lock_until or try_shared_lock_until respectively. + (try_lock_until, try_shared_lock_until): Wait on the condition + variables until the specified time passes. + (unlock): Add Debug Mode assertion. + (unlock_shared): Add Debug Mode assertion. + * testsuite/30_threads/shared_timed_mutex/try_lock/3.cc: New. + 2015-04-09 H.J. Lu * config/abi/post/x86_64-linux-gnu/x32/baseline_symbols.txt: Update. diff --git a/libstdc++-v3/include/std/shared_mutex b/libstdc++-v3/include/std/shared_mutex index ab1b45b87ac..7f26465788b 100644 --- a/libstdc++-v3/include/std/shared_mutex +++ b/libstdc++-v3/include/std/shared_mutex @@ -268,33 +268,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #else // ! _GLIBCXX_USE_PTHREAD_RWLOCK_T -#if _GTHREAD_USE_MUTEX_TIMEDLOCK - struct _Mutex : mutex, __timed_mutex_impl<_Mutex> - { - template - bool - try_lock_for(const chrono::duration<_Rep, _Period>& __rtime) - { return _M_try_lock_for(__rtime); } - - template - bool - try_lock_until(const chrono::time_point<_Clock, _Duration>& __atime) - { return _M_try_lock_until(__atime); } - }; -#else - typedef mutex _Mutex; -#endif - - // Based on Howard Hinnant's reference implementation from N2406 + // Must use the same clock as condition_variable + typedef chrono::system_clock __clock_t; - _Mutex _M_mut; + // Based on Howard Hinnant's reference implementation from N2406. + + // The high bit of _M_state is the write-entered flag which is set to + // indicate a writer has taken the lock or is queuing to take the lock. + // The remaining bits are the count of reader locks. + // + // To take a reader lock, block on gate1 while the write-entered flag is + // set or the maximum number of reader locks is held, then increment the + // reader lock count. + // To release, decrement the count, then if the write-entered flag is set + // and the count is zero then signal gate2 to wake a queued writer, + // otherwise if the maximum number of reader locks was held signal gate1 + // to wake a reader. + // + // To take a writer lock, block on gate1 while the write-entered flag is + // set, then set the write-entered flag to start queueing, then block on + // gate2 while the number of reader locks is non-zero. + // To release, unset the write-entered flag and signal gate1 to wake all + // blocked readers and writers. + // + // This means that when no reader locks are held readers and writers get + // equal priority. When one or more reader locks is held a writer gets + // priority and no more reader locks can be taken while the writer is + // queued. + + // Only locked when accessing _M_state or waiting on condition variables. + mutex _M_mut; + // Used to block while write-entered is set or reader count at maximum. condition_variable _M_gate1; + // Used to block queued writers while reader count is non-zero. condition_variable _M_gate2; + // The write-entered flag and reader count. unsigned _M_state; static constexpr unsigned _S_write_entered = 1U << (sizeof(unsigned)*__CHAR_BIT__ - 1); - static constexpr unsigned _M_n_readers = ~_S_write_entered; + static constexpr unsigned _S_max_readers = ~_S_write_entered; + + // Test whether the write-entered flag is set. _M_mut must be locked. + bool _M_write_entered() const { return _M_state & _S_write_entered; } + + // The number of reader locks currently held. _M_mut must be locked. + unsigned _M_readers() const { return _M_state & _S_max_readers; } public: shared_timed_mutex() : _M_state(0) {} @@ -313,11 +332,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION lock() { unique_lock __lk(_M_mut); - while (_M_state & _S_write_entered) - _M_gate1.wait(__lk); + // Wait until we can set the write-entered flag. + _M_gate1.wait(__lk, [=]{ return !_M_write_entered(); }); _M_state |= _S_write_entered; - while (_M_state & _M_n_readers) - _M_gate2.wait(__lk); + // Then wait until there are no more readers. + _M_gate2.wait(__lk, [=]{ return _M_readers() == 0; }); } bool @@ -332,41 +351,43 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION return false; } -#if _GTHREAD_USE_MUTEX_TIMEDLOCK template bool try_lock_for(const chrono::duration<_Rep, _Period>& __rel_time) { - unique_lock<_Mutex> __lk(_M_mut, __rel_time); - if (__lk.owns_lock() && _M_state == 0) - { - _M_state = _S_write_entered; - return true; - } - return false; + return try_lock_until(__clock_t::now() + __rel_time); } template bool try_lock_until(const chrono::time_point<_Clock, _Duration>& __abs_time) { - unique_lock<_Mutex> __lk(_M_mut, __abs_time); - if (__lk.owns_lock() && _M_state == 0) + unique_lock __lk(_M_mut); + if (!_M_gate1.wait_until(__lk, __abs_time, + [=]{ return !_M_write_entered(); })) { - _M_state = _S_write_entered; - return true; + return false; } - return false; + _M_state |= _S_write_entered; + if (!_M_gate2.wait_until(__lk, __abs_time, + [=]{ return _M_readers() == 0; })) + { + _M_state ^= _S_write_entered; + // Wake all threads blocked while the write-entered flag was set. + _M_gate1.notify_all(); + return false; + } + return true; } -#endif void unlock() { - { - lock_guard<_Mutex> __lk(_M_mut); - _M_state = 0; - } + lock_guard __lk(_M_mut); + _GLIBCXX_DEBUG_ASSERT( _M_write_entered() ); + _M_state = 0; + // call notify_all() while mutex is held so that another thread can't + // lock and unlock the mutex then destroy *this before we make the call. _M_gate1.notify_all(); } @@ -376,51 +397,29 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION lock_shared() { unique_lock __lk(_M_mut); - while ((_M_state & _S_write_entered) - || (_M_state & _M_n_readers) == _M_n_readers) - { - _M_gate1.wait(__lk); - } - unsigned __num_readers = (_M_state & _M_n_readers) + 1; - _M_state &= ~_M_n_readers; - _M_state |= __num_readers; + _M_gate1.wait(__lk, [=]{ return _M_state < _S_max_readers; }); + ++_M_state; } bool try_lock_shared() { - unique_lock<_Mutex> __lk(_M_mut, try_to_lock); - unsigned __num_readers = _M_state & _M_n_readers; - if (__lk.owns_lock() && !(_M_state & _S_write_entered) - && __num_readers != _M_n_readers) + unique_lock __lk(_M_mut, try_to_lock); + if (!__lk.owns_lock()) + return false; + if (_M_state < _S_max_readers) { - ++__num_readers; - _M_state &= ~_M_n_readers; - _M_state |= __num_readers; + ++_M_state; return true; } return false; } -#if _GTHREAD_USE_MUTEX_TIMEDLOCK template bool try_lock_shared_for(const chrono::duration<_Rep, _Period>& __rel_time) { - unique_lock<_Mutex> __lk(_M_mut, __rel_time); - if (__lk.owns_lock()) - { - unsigned __num_readers = _M_state & _M_n_readers; - if (!(_M_state & _S_write_entered) - && __num_readers != _M_n_readers) - { - ++__num_readers; - _M_state &= ~_M_n_readers; - _M_state |= __num_readers; - return true; - } - } - return false; + return try_lock_shared_until(__clock_t::now() + __rel_time); } template @@ -428,38 +427,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION try_lock_shared_until(const chrono::time_point<_Clock, _Duration>& __abs_time) { - unique_lock<_Mutex> __lk(_M_mut, __abs_time); - if (__lk.owns_lock()) + unique_lock __lk(_M_mut); + if (!_M_gate1.wait_until(__lk, __abs_time, + [=]{ return _M_state < _S_max_readers; })) { - unsigned __num_readers = _M_state & _M_n_readers; - if (!(_M_state & _S_write_entered) - && __num_readers != _M_n_readers) - { - ++__num_readers; - _M_state &= ~_M_n_readers; - _M_state |= __num_readers; - return true; - } + return false; } - return false; + ++_M_state; + return true; } -#endif void unlock_shared() { - lock_guard<_Mutex> __lk(_M_mut); - unsigned __num_readers = (_M_state & _M_n_readers) - 1; - _M_state &= ~_M_n_readers; - _M_state |= __num_readers; - if (_M_state & _S_write_entered) + lock_guard __lk(_M_mut); + _GLIBCXX_DEBUG_ASSERT( _M_readers() > 0 ); + auto __prev = _M_state--; + if (_M_write_entered()) { - if (__num_readers == 0) + // Wake the queued writer if there are no more readers. + if (_M_readers() == 0) _M_gate2.notify_one(); + // No need to notify gate1 because we give priority to the queued + // writer, and that writer will eventually notify gate1 after it + // clears the write-entered flag. } else { - if (__num_readers == _M_n_readers - 1) + // Wake any thread that was blocked on reader overflow. + if (__prev == _S_max_readers) _M_gate1.notify_one(); } } diff --git a/libstdc++-v3/testsuite/30_threads/shared_timed_mutex/try_lock/3.cc b/libstdc++-v3/testsuite/30_threads/shared_timed_mutex/try_lock/3.cc new file mode 100644 index 00000000000..e9f728e0955 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/shared_timed_mutex/try_lock/3.cc @@ -0,0 +1,75 @@ +// { dg-do run { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-solaris* *-*-cygwin *-*-darwin* powerpc-ibm-aix* } } +// { dg-options " -std=gnu++14 -pthread" { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* powerpc-ibm-aix* } } +// { dg-options " -std=gnu++14 -pthreads" { target *-*-solaris* } } +// { dg-options " -std=gnu++14 " { target *-*-cygwin *-*-darwin* } } +// { dg-require-cstdint "" } +// { dg-require-gthreads "" } + +// Copyright (C) 2013-2015 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// . + + +#include +#include +#include +#include + +int main() +{ + bool test __attribute__((unused)) = true; + typedef std::shared_timed_mutex mutex_type; + + try + { + mutex_type m; + m.lock(); + bool b; + + std::thread t([&] { + try + { + using namespace std::chrono; + auto timeout = 100ms; + auto start = system_clock::now(); + b = m.try_lock_for(timeout); + auto t = system_clock::now() - start; + VERIFY( !b ); + VERIFY( t >= timeout ); + + start = system_clock::now(); + b = m.try_lock_until(start + timeout); + t = system_clock::now() - start; + VERIFY( !b ); + VERIFY( t >= timeout ); + } + catch (const std::system_error& e) + { + VERIFY( false ); + } + }); + t.join(); + m.unlock(); + } + catch (const std::system_error& e) + { + VERIFY( false ); + } + catch (...) + { + VERIFY( false ); + } +}