149e08aacStbbdev /*
2*c21e688aSSergey Zheltov Copyright (c) 2005-2022 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.
queuing_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".
reset()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.
scoped_lock(queuing_mutex & m)6049e08aacStbbdev scoped_lock(queuing_mutex& m) {
6149e08aacStbbdev acquire(m);
6249e08aacStbbdev }
6349e08aacStbbdev
6449e08aacStbbdev //! Release lock (if lock is held).
~scoped_lock()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.
acquire(queuing_mutex & m)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)
try_acquire(queuing_mutex & m)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.
release()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:
14257f524caSIlya 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
set_name(queuing_mutex & obj,const char * name)16749e08aacStbbdev inline void set_name(queuing_mutex& obj, const char* name) {
16849e08aacStbbdev itt_set_sync_name(&obj, name);
16949e08aacStbbdev }
1708827ea7dSLong Nguyen #if (_WIN32||_WIN64)
set_name(queuing_mutex & obj,const wchar_t * name)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
set_name(queuing_mutex &,const char *)17649e08aacStbbdev inline void set_name(queuing_mutex&, const char*) {}
1778827ea7dSLong Nguyen #if (_WIN32||_WIN64)
set_name(queuing_mutex &,const wchar_t *)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