149e08aacStbbdev /* 2*b15aabb3Stbbdev 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" 2349e08aacStbbdev 2449e08aacStbbdev #include "profiling.h" 2549e08aacStbbdev 2649e08aacStbbdev #include <atomic> 2749e08aacStbbdev 2849e08aacStbbdev namespace tbb { 2949e08aacStbbdev namespace detail { 3049e08aacStbbdev namespace d1 { 3149e08aacStbbdev 3249e08aacStbbdev //! Queuing mutex with local-only spinning. 3349e08aacStbbdev /** @ingroup synchronization */ 3449e08aacStbbdev class queuing_mutex { 3549e08aacStbbdev public: 3649e08aacStbbdev //! Construct unacquired mutex. 3749e08aacStbbdev queuing_mutex() noexcept { 3849e08aacStbbdev create_itt_sync(this, "tbb::queuing_mutex", ""); 3949e08aacStbbdev }; 4049e08aacStbbdev 4149e08aacStbbdev queuing_mutex(const queuing_mutex&) = delete; 4249e08aacStbbdev queuing_mutex& operator=(const queuing_mutex&) = delete; 4349e08aacStbbdev 4449e08aacStbbdev //! The scoped locking pattern 4549e08aacStbbdev /** It helps to avoid the common problem of forgetting to release lock. 4649e08aacStbbdev It also nicely provides the "node" for queuing locks. */ 4749e08aacStbbdev class scoped_lock { 4849e08aacStbbdev //! Reset fields to mean "no lock held". 4949e08aacStbbdev void reset() { 5049e08aacStbbdev m_mutex = nullptr; 5149e08aacStbbdev } 5249e08aacStbbdev 5349e08aacStbbdev public: 5449e08aacStbbdev //! Construct lock that has not acquired a mutex. 5549e08aacStbbdev /** Equivalent to zero-initialization of *this. */ 5649e08aacStbbdev scoped_lock() = default; 5749e08aacStbbdev 5849e08aacStbbdev //! Acquire lock on given mutex. 5949e08aacStbbdev scoped_lock(queuing_mutex& m) { 6049e08aacStbbdev acquire(m); 6149e08aacStbbdev } 6249e08aacStbbdev 6349e08aacStbbdev //! Release lock (if lock is held). 6449e08aacStbbdev ~scoped_lock() { 6549e08aacStbbdev if (m_mutex) release(); 6649e08aacStbbdev } 6749e08aacStbbdev 6849e08aacStbbdev //! No Copy 6949e08aacStbbdev scoped_lock( const scoped_lock& ) = delete; 7049e08aacStbbdev scoped_lock& operator=( const scoped_lock& ) = delete; 7149e08aacStbbdev 7249e08aacStbbdev //! Acquire lock on given mutex. 7349e08aacStbbdev void acquire( queuing_mutex& m ) { 7449e08aacStbbdev __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex"); 7549e08aacStbbdev 7649e08aacStbbdev // Must set all fields before the exchange, because once the 7749e08aacStbbdev // exchange executes, *this becomes accessible to other threads. 7849e08aacStbbdev m_mutex = &m; 7949e08aacStbbdev m_next.store(nullptr, std::memory_order_relaxed); 8049e08aacStbbdev m_going.store(0U, std::memory_order_relaxed); 8149e08aacStbbdev 8249e08aacStbbdev // x86 compare exchange operation always has a strong fence 8349e08aacStbbdev // "sending" the fields initialized above to other processors. 8449e08aacStbbdev scoped_lock* pred = m.q_tail.exchange(this); 8549e08aacStbbdev if (pred) { 8649e08aacStbbdev call_itt_notify(prepare, &m); 8749e08aacStbbdev __TBB_ASSERT(pred->m_next.load(std::memory_order_relaxed) == nullptr, "the predecessor has another successor!"); 8849e08aacStbbdev 8949e08aacStbbdev pred->m_next.store(this, std::memory_order_relaxed); 9049e08aacStbbdev spin_wait_while_eq(m_going, 0U); 9149e08aacStbbdev } 9249e08aacStbbdev call_itt_notify(acquired, &m); 9349e08aacStbbdev 9449e08aacStbbdev // Force acquire so that user's critical section receives correct values 9549e08aacStbbdev // from processor that was previously in the user's critical section. 9649e08aacStbbdev atomic_fence(std::memory_order_acquire); 9749e08aacStbbdev } 9849e08aacStbbdev 9949e08aacStbbdev //! Acquire lock on given mutex if free (i.e. non-blocking) 10049e08aacStbbdev bool try_acquire( queuing_mutex& m ) { 10149e08aacStbbdev __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex"); 10249e08aacStbbdev 10349e08aacStbbdev // Must set all fields before the compare_exchange_strong, because once the 10449e08aacStbbdev // compare_exchange_strong executes, *this becomes accessible to other threads. 10549e08aacStbbdev m_next.store(nullptr, std::memory_order_relaxed); 10649e08aacStbbdev m_going.store(0U, std::memory_order_relaxed); 10749e08aacStbbdev 10849e08aacStbbdev scoped_lock* expected = nullptr; 10949e08aacStbbdev // The compare_exchange_strong must have release semantics, because we are 11049e08aacStbbdev // "sending" the fields initialized above to other processors. 11149e08aacStbbdev // x86 compare exchange operation always has a strong fence 11249e08aacStbbdev if (!m.q_tail.compare_exchange_strong(expected, this)) 11349e08aacStbbdev return false; 11449e08aacStbbdev 11549e08aacStbbdev m_mutex = &m; 11649e08aacStbbdev 11749e08aacStbbdev // Force acquire so that user's critical section receives correct values 11849e08aacStbbdev // from processor that was previously in the user's critical section. 11949e08aacStbbdev atomic_fence(std::memory_order_acquire); 12049e08aacStbbdev call_itt_notify(acquired, &m); 12149e08aacStbbdev return true; 12249e08aacStbbdev } 12349e08aacStbbdev 12449e08aacStbbdev //! Release lock. 12549e08aacStbbdev void release() 12649e08aacStbbdev { 12749e08aacStbbdev __TBB_ASSERT(this->m_mutex, "no lock acquired"); 12849e08aacStbbdev 12949e08aacStbbdev call_itt_notify(releasing, this->m_mutex); 13049e08aacStbbdev 13149e08aacStbbdev if (m_next.load(std::memory_order_relaxed) == nullptr) { 13249e08aacStbbdev scoped_lock* expected = this; 13349e08aacStbbdev if (m_mutex->q_tail.compare_exchange_strong(expected, nullptr)) { 13449e08aacStbbdev // this was the only item in the queue, and the queue is now empty. 13549e08aacStbbdev reset(); 13649e08aacStbbdev return; 13749e08aacStbbdev } 13849e08aacStbbdev // Someone in the queue 13949e08aacStbbdev spin_wait_while_eq(m_next, nullptr); 14049e08aacStbbdev } 14149e08aacStbbdev m_next.load(std::memory_order_relaxed)->m_going.store(1U, std::memory_order_release); 14249e08aacStbbdev 14349e08aacStbbdev reset(); 14449e08aacStbbdev } 14549e08aacStbbdev 14649e08aacStbbdev private: 14749e08aacStbbdev //! The pointer to the mutex owned, or NULL if not holding a mutex. 14849e08aacStbbdev queuing_mutex* m_mutex{nullptr}; 14949e08aacStbbdev 15049e08aacStbbdev //! The pointer to the next competitor for a mutex 15149e08aacStbbdev std::atomic<scoped_lock*> m_next{nullptr}; 15249e08aacStbbdev 15349e08aacStbbdev //! The local spin-wait variable 15449e08aacStbbdev /** Inverted (0 - blocked, 1 - acquired the mutex) for the sake of 15549e08aacStbbdev zero-initialization. Defining it as an entire word instead of 15649e08aacStbbdev a byte seems to help performance slightly. */ 15749e08aacStbbdev std::atomic<uintptr_t> m_going{0U}; 15849e08aacStbbdev }; 15949e08aacStbbdev 16049e08aacStbbdev // Mutex traits 16149e08aacStbbdev static constexpr bool is_rw_mutex = false; 16249e08aacStbbdev static constexpr bool is_recursive_mutex = false; 16349e08aacStbbdev static constexpr bool is_fair_mutex = true; 16449e08aacStbbdev 16549e08aacStbbdev private: 16649e08aacStbbdev //! The last competitor requesting the lock 16749e08aacStbbdev std::atomic<scoped_lock*> q_tail{nullptr}; 16849e08aacStbbdev 16949e08aacStbbdev }; 17049e08aacStbbdev 17149e08aacStbbdev #if TBB_USE_PROFILING_TOOLS 17249e08aacStbbdev inline void set_name(queuing_mutex& obj, const char* name) { 17349e08aacStbbdev itt_set_sync_name(&obj, name); 17449e08aacStbbdev } 17549e08aacStbbdev #if (_WIN32||_WIN64) && !__MINGW32__ 17649e08aacStbbdev inline void set_name(queuing_mutex& obj, const wchar_t* name) { 17749e08aacStbbdev itt_set_sync_name(&obj, name); 17849e08aacStbbdev } 17949e08aacStbbdev #endif //WIN 18049e08aacStbbdev #else 18149e08aacStbbdev inline void set_name(queuing_mutex&, const char*) {} 18249e08aacStbbdev #if (_WIN32||_WIN64) && !__MINGW32__ 18349e08aacStbbdev inline void set_name(queuing_mutex&, const wchar_t*) {} 18449e08aacStbbdev #endif //WIN 18549e08aacStbbdev #endif 18649e08aacStbbdev } // namespace d1 18749e08aacStbbdev } // namespace detail 18849e08aacStbbdev 18949e08aacStbbdev inline namespace v1 { 19049e08aacStbbdev using detail::d1::queuing_mutex; 19149e08aacStbbdev } // namespace v1 19249e08aacStbbdev namespace profiling { 19349e08aacStbbdev using detail::d1::set_name; 19449e08aacStbbdev } 19549e08aacStbbdev } // namespace tbb 19649e08aacStbbdev 19749e08aacStbbdev #endif /* __TBB_queuing_mutex_H */ 198