xref: /oneTBB/include/oneapi/tbb/queuing_mutex.h (revision b15aabb3)
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