149e08aacStbbdev /* 2b15aabb3Stbbdev Copyright (c) 2005-2021 Intel Corporation 349e08aacStbbdev 449e08aacStbbdev Licensed under the Apache License, Version 2.0 (the "License"); 549e08aacStbbdev you may not use this file except in compliance with the License. 649e08aacStbbdev You may obtain a copy of the License at 749e08aacStbbdev 849e08aacStbbdev http://www.apache.org/licenses/LICENSE-2.0 949e08aacStbbdev 1049e08aacStbbdev Unless required by applicable law or agreed to in writing, software 1149e08aacStbbdev distributed under the License is distributed on an "AS IS" BASIS, 1249e08aacStbbdev WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1349e08aacStbbdev See the License for the specific language governing permissions and 1449e08aacStbbdev limitations under the License. 1549e08aacStbbdev */ 1649e08aacStbbdev 1749e08aacStbbdev #ifndef __TBB_queuing_mutex_H 1849e08aacStbbdev #define __TBB_queuing_mutex_H 1949e08aacStbbdev 2049e08aacStbbdev #include "detail/_namespace_injection.h" 2149e08aacStbbdev #include "detail/_assert.h" 2249e08aacStbbdev #include "detail/_utils.h" 23478de5b1Stbbdev #include "detail/_mutex_common.h" 2449e08aacStbbdev 2549e08aacStbbdev #include "profiling.h" 2649e08aacStbbdev 2749e08aacStbbdev #include <atomic> 2849e08aacStbbdev 2949e08aacStbbdev namespace tbb { 3049e08aacStbbdev namespace detail { 3149e08aacStbbdev namespace d1 { 3249e08aacStbbdev 3349e08aacStbbdev //! Queuing mutex with local-only spinning. 3449e08aacStbbdev /** @ingroup synchronization */ 3549e08aacStbbdev class queuing_mutex { 3649e08aacStbbdev public: 3749e08aacStbbdev //! Construct unacquired mutex. 3849e08aacStbbdev queuing_mutex() noexcept { 3949e08aacStbbdev create_itt_sync(this, "tbb::queuing_mutex", ""); 4049e08aacStbbdev }; 4149e08aacStbbdev 4249e08aacStbbdev queuing_mutex(const queuing_mutex&) = delete; 4349e08aacStbbdev queuing_mutex& operator=(const queuing_mutex&) = delete; 4449e08aacStbbdev 4549e08aacStbbdev //! The scoped locking pattern 4649e08aacStbbdev /** It helps to avoid the common problem of forgetting to release lock. 4749e08aacStbbdev It also nicely provides the "node" for queuing locks. */ 4849e08aacStbbdev class scoped_lock { 4949e08aacStbbdev //! Reset fields to mean "no lock held". 5049e08aacStbbdev void reset() { 5149e08aacStbbdev m_mutex = nullptr; 5249e08aacStbbdev } 5349e08aacStbbdev 5449e08aacStbbdev public: 5549e08aacStbbdev //! Construct lock that has not acquired a mutex. 5649e08aacStbbdev /** Equivalent to zero-initialization of *this. */ 5749e08aacStbbdev scoped_lock() = default; 5849e08aacStbbdev 5949e08aacStbbdev //! Acquire lock on given mutex. 6049e08aacStbbdev scoped_lock(queuing_mutex& m) { 6149e08aacStbbdev acquire(m); 6249e08aacStbbdev } 6349e08aacStbbdev 6449e08aacStbbdev //! Release lock (if lock is held). 6549e08aacStbbdev ~scoped_lock() { 6649e08aacStbbdev if (m_mutex) release(); 6749e08aacStbbdev } 6849e08aacStbbdev 6949e08aacStbbdev //! No Copy 7049e08aacStbbdev scoped_lock( const scoped_lock& ) = delete; 7149e08aacStbbdev scoped_lock& operator=( const scoped_lock& ) = delete; 7249e08aacStbbdev 7349e08aacStbbdev //! Acquire lock on given mutex. 7449e08aacStbbdev void acquire( queuing_mutex& m ) { 7549e08aacStbbdev __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex"); 7649e08aacStbbdev 7749e08aacStbbdev // Must set all fields before the exchange, because once the 7849e08aacStbbdev // exchange executes, *this becomes accessible to other threads. 7949e08aacStbbdev m_mutex = &m; 8049e08aacStbbdev m_next.store(nullptr, std::memory_order_relaxed); 8149e08aacStbbdev m_going.store(0U, std::memory_order_relaxed); 8249e08aacStbbdev 8349e08aacStbbdev // x86 compare exchange operation always has a strong fence 8449e08aacStbbdev // "sending" the fields initialized above to other processors. 8549e08aacStbbdev scoped_lock* pred = m.q_tail.exchange(this); 8649e08aacStbbdev if (pred) { 8749e08aacStbbdev call_itt_notify(prepare, &m); 8849e08aacStbbdev __TBB_ASSERT(pred->m_next.load(std::memory_order_relaxed) == nullptr, "the predecessor has another successor!"); 8949e08aacStbbdev 90478de5b1Stbbdev pred->m_next.store(this, std::memory_order_release); 9149e08aacStbbdev spin_wait_while_eq(m_going, 0U); 9249e08aacStbbdev } 9349e08aacStbbdev call_itt_notify(acquired, &m); 9449e08aacStbbdev 9549e08aacStbbdev } 9649e08aacStbbdev 9749e08aacStbbdev //! Acquire lock on given mutex if free (i.e. non-blocking) 9849e08aacStbbdev bool try_acquire( queuing_mutex& m ) { 9949e08aacStbbdev __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex"); 10049e08aacStbbdev 10149e08aacStbbdev // Must set all fields before the compare_exchange_strong, because once the 10249e08aacStbbdev // compare_exchange_strong executes, *this becomes accessible to other threads. 10349e08aacStbbdev m_next.store(nullptr, std::memory_order_relaxed); 10449e08aacStbbdev m_going.store(0U, std::memory_order_relaxed); 10549e08aacStbbdev 10649e08aacStbbdev scoped_lock* expected = nullptr; 10749e08aacStbbdev // The compare_exchange_strong must have release semantics, because we are 10849e08aacStbbdev // "sending" the fields initialized above to other processors. 10949e08aacStbbdev // x86 compare exchange operation always has a strong fence 110478de5b1Stbbdev if (!m.q_tail.compare_exchange_strong(expected, this, std::memory_order_acq_rel)) 11149e08aacStbbdev return false; 11249e08aacStbbdev 11349e08aacStbbdev m_mutex = &m; 11449e08aacStbbdev 11549e08aacStbbdev call_itt_notify(acquired, &m); 11649e08aacStbbdev return true; 11749e08aacStbbdev } 11849e08aacStbbdev 11949e08aacStbbdev //! Release lock. 12049e08aacStbbdev void release() 12149e08aacStbbdev { 12249e08aacStbbdev __TBB_ASSERT(this->m_mutex, "no lock acquired"); 12349e08aacStbbdev 12449e08aacStbbdev call_itt_notify(releasing, this->m_mutex); 12549e08aacStbbdev 12649e08aacStbbdev if (m_next.load(std::memory_order_relaxed) == nullptr) { 12749e08aacStbbdev scoped_lock* expected = this; 12849e08aacStbbdev if (m_mutex->q_tail.compare_exchange_strong(expected, nullptr)) { 12949e08aacStbbdev // this was the only item in the queue, and the queue is now empty. 13049e08aacStbbdev reset(); 13149e08aacStbbdev return; 13249e08aacStbbdev } 13349e08aacStbbdev // Someone in the queue 13449e08aacStbbdev spin_wait_while_eq(m_next, nullptr); 13549e08aacStbbdev } 136478de5b1Stbbdev m_next.load(std::memory_order_acquire)->m_going.store(1U, std::memory_order_release); 13749e08aacStbbdev 13849e08aacStbbdev reset(); 13949e08aacStbbdev } 14049e08aacStbbdev 14149e08aacStbbdev private: 142*57f524caSIlya Isaev //! The pointer to the mutex owned, or nullptr if not holding a mutex. 14349e08aacStbbdev queuing_mutex* m_mutex{nullptr}; 14449e08aacStbbdev 14549e08aacStbbdev //! The pointer to the next competitor for a mutex 14649e08aacStbbdev std::atomic<scoped_lock*> m_next{nullptr}; 14749e08aacStbbdev 14849e08aacStbbdev //! The local spin-wait variable 14949e08aacStbbdev /** Inverted (0 - blocked, 1 - acquired the mutex) for the sake of 15049e08aacStbbdev zero-initialization. Defining it as an entire word instead of 15149e08aacStbbdev a byte seems to help performance slightly. */ 15249e08aacStbbdev std::atomic<uintptr_t> m_going{0U}; 15349e08aacStbbdev }; 15449e08aacStbbdev 15549e08aacStbbdev // Mutex traits 15649e08aacStbbdev static constexpr bool is_rw_mutex = false; 15749e08aacStbbdev static constexpr bool is_recursive_mutex = false; 15849e08aacStbbdev static constexpr bool is_fair_mutex = true; 15949e08aacStbbdev 16049e08aacStbbdev private: 16149e08aacStbbdev //! The last competitor requesting the lock 16249e08aacStbbdev std::atomic<scoped_lock*> q_tail{nullptr}; 16349e08aacStbbdev 16449e08aacStbbdev }; 16549e08aacStbbdev 16649e08aacStbbdev #if TBB_USE_PROFILING_TOOLS 16749e08aacStbbdev inline void set_name(queuing_mutex& obj, const char* name) { 16849e08aacStbbdev itt_set_sync_name(&obj, name); 16949e08aacStbbdev } 1708827ea7dSLong Nguyen #if (_WIN32||_WIN64) 17149e08aacStbbdev inline void set_name(queuing_mutex& obj, const wchar_t* name) { 17249e08aacStbbdev itt_set_sync_name(&obj, name); 17349e08aacStbbdev } 17449e08aacStbbdev #endif //WIN 17549e08aacStbbdev #else 17649e08aacStbbdev inline void set_name(queuing_mutex&, const char*) {} 1778827ea7dSLong Nguyen #if (_WIN32||_WIN64) 17849e08aacStbbdev inline void set_name(queuing_mutex&, const wchar_t*) {} 17949e08aacStbbdev #endif //WIN 18049e08aacStbbdev #endif 18149e08aacStbbdev } // namespace d1 18249e08aacStbbdev } // namespace detail 18349e08aacStbbdev 18449e08aacStbbdev inline namespace v1 { 18549e08aacStbbdev using detail::d1::queuing_mutex; 18649e08aacStbbdev } // namespace v1 18749e08aacStbbdev namespace profiling { 18849e08aacStbbdev using detail::d1::set_name; 18949e08aacStbbdev } 19049e08aacStbbdev } // namespace tbb 19149e08aacStbbdev 19249e08aacStbbdev #endif /* __TBB_queuing_mutex_H */ 193