xref: /oneTBB/include/oneapi/tbb/queuing_mutex.h (revision 49e08aac)
1*49e08aacStbbdev /*
2*49e08aacStbbdev     Copyright (c) 2005-2020 Intel Corporation
3*49e08aacStbbdev 
4*49e08aacStbbdev     Licensed under the Apache License, Version 2.0 (the "License");
5*49e08aacStbbdev     you may not use this file except in compliance with the License.
6*49e08aacStbbdev     You may obtain a copy of the License at
7*49e08aacStbbdev 
8*49e08aacStbbdev         http://www.apache.org/licenses/LICENSE-2.0
9*49e08aacStbbdev 
10*49e08aacStbbdev     Unless required by applicable law or agreed to in writing, software
11*49e08aacStbbdev     distributed under the License is distributed on an "AS IS" BASIS,
12*49e08aacStbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*49e08aacStbbdev     See the License for the specific language governing permissions and
14*49e08aacStbbdev     limitations under the License.
15*49e08aacStbbdev */
16*49e08aacStbbdev 
17*49e08aacStbbdev #ifndef __TBB_queuing_mutex_H
18*49e08aacStbbdev #define __TBB_queuing_mutex_H
19*49e08aacStbbdev 
20*49e08aacStbbdev #include "detail/_namespace_injection.h"
21*49e08aacStbbdev #include "detail/_assert.h"
22*49e08aacStbbdev #include "detail/_utils.h"
23*49e08aacStbbdev 
24*49e08aacStbbdev #include "profiling.h"
25*49e08aacStbbdev 
26*49e08aacStbbdev #include <atomic>
27*49e08aacStbbdev 
28*49e08aacStbbdev namespace tbb {
29*49e08aacStbbdev namespace detail {
30*49e08aacStbbdev namespace d1 {
31*49e08aacStbbdev 
32*49e08aacStbbdev //! Queuing mutex with local-only spinning.
33*49e08aacStbbdev /** @ingroup synchronization */
34*49e08aacStbbdev class queuing_mutex {
35*49e08aacStbbdev public:
36*49e08aacStbbdev     //! Construct unacquired mutex.
37*49e08aacStbbdev     queuing_mutex() noexcept  {
38*49e08aacStbbdev         create_itt_sync(this, "tbb::queuing_mutex", "");
39*49e08aacStbbdev     };
40*49e08aacStbbdev 
41*49e08aacStbbdev     queuing_mutex(const queuing_mutex&) = delete;
42*49e08aacStbbdev     queuing_mutex& operator=(const queuing_mutex&) = delete;
43*49e08aacStbbdev 
44*49e08aacStbbdev     //! The scoped locking pattern
45*49e08aacStbbdev     /** It helps to avoid the common problem of forgetting to release lock.
46*49e08aacStbbdev         It also nicely provides the "node" for queuing locks. */
47*49e08aacStbbdev     class scoped_lock {
48*49e08aacStbbdev         //! Reset fields to mean "no lock held".
49*49e08aacStbbdev         void reset() {
50*49e08aacStbbdev             m_mutex = nullptr;
51*49e08aacStbbdev         }
52*49e08aacStbbdev 
53*49e08aacStbbdev     public:
54*49e08aacStbbdev         //! Construct lock that has not acquired a mutex.
55*49e08aacStbbdev         /** Equivalent to zero-initialization of *this. */
56*49e08aacStbbdev         scoped_lock() = default;
57*49e08aacStbbdev 
58*49e08aacStbbdev         //! Acquire lock on given mutex.
59*49e08aacStbbdev         scoped_lock(queuing_mutex& m) {
60*49e08aacStbbdev             acquire(m);
61*49e08aacStbbdev         }
62*49e08aacStbbdev 
63*49e08aacStbbdev         //! Release lock (if lock is held).
64*49e08aacStbbdev         ~scoped_lock() {
65*49e08aacStbbdev             if (m_mutex) release();
66*49e08aacStbbdev         }
67*49e08aacStbbdev 
68*49e08aacStbbdev         //! No Copy
69*49e08aacStbbdev         scoped_lock( const scoped_lock& ) = delete;
70*49e08aacStbbdev         scoped_lock& operator=( const scoped_lock& ) = delete;
71*49e08aacStbbdev 
72*49e08aacStbbdev         //! Acquire lock on given mutex.
73*49e08aacStbbdev         void acquire( queuing_mutex& m ) {
74*49e08aacStbbdev             __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex");
75*49e08aacStbbdev 
76*49e08aacStbbdev             // Must set all fields before the exchange, because once the
77*49e08aacStbbdev             // exchange executes, *this becomes accessible to other threads.
78*49e08aacStbbdev             m_mutex = &m;
79*49e08aacStbbdev             m_next.store(nullptr, std::memory_order_relaxed);
80*49e08aacStbbdev             m_going.store(0U, std::memory_order_relaxed);
81*49e08aacStbbdev 
82*49e08aacStbbdev             // x86 compare exchange operation always has a strong fence
83*49e08aacStbbdev             // "sending" the fields initialized above to other processors.
84*49e08aacStbbdev             scoped_lock* pred = m.q_tail.exchange(this);
85*49e08aacStbbdev             if (pred) {
86*49e08aacStbbdev                 call_itt_notify(prepare, &m);
87*49e08aacStbbdev                 __TBB_ASSERT(pred->m_next.load(std::memory_order_relaxed) == nullptr, "the predecessor has another successor!");
88*49e08aacStbbdev 
89*49e08aacStbbdev                 pred->m_next.store(this, std::memory_order_relaxed);
90*49e08aacStbbdev                 spin_wait_while_eq(m_going, 0U);
91*49e08aacStbbdev             }
92*49e08aacStbbdev             call_itt_notify(acquired, &m);
93*49e08aacStbbdev 
94*49e08aacStbbdev             // Force acquire so that user's critical section receives correct values
95*49e08aacStbbdev             // from processor that was previously in the user's critical section.
96*49e08aacStbbdev             atomic_fence(std::memory_order_acquire);
97*49e08aacStbbdev         }
98*49e08aacStbbdev 
99*49e08aacStbbdev         //! Acquire lock on given mutex if free (i.e. non-blocking)
100*49e08aacStbbdev         bool try_acquire( queuing_mutex& m ) {
101*49e08aacStbbdev             __TBB_ASSERT(!m_mutex, "scoped_lock is already holding a mutex");
102*49e08aacStbbdev 
103*49e08aacStbbdev             // Must set all fields before the compare_exchange_strong, because once the
104*49e08aacStbbdev             // compare_exchange_strong executes, *this becomes accessible to other threads.
105*49e08aacStbbdev             m_next.store(nullptr, std::memory_order_relaxed);
106*49e08aacStbbdev             m_going.store(0U, std::memory_order_relaxed);
107*49e08aacStbbdev 
108*49e08aacStbbdev             scoped_lock* expected = nullptr;
109*49e08aacStbbdev             // The compare_exchange_strong must have release semantics, because we are
110*49e08aacStbbdev             // "sending" the fields initialized above to other processors.
111*49e08aacStbbdev             // x86 compare exchange operation always has a strong fence
112*49e08aacStbbdev             if (!m.q_tail.compare_exchange_strong(expected, this))
113*49e08aacStbbdev                 return false;
114*49e08aacStbbdev 
115*49e08aacStbbdev             m_mutex = &m;
116*49e08aacStbbdev 
117*49e08aacStbbdev             // Force acquire so that user's critical section receives correct values
118*49e08aacStbbdev             // from processor that was previously in the user's critical section.
119*49e08aacStbbdev             atomic_fence(std::memory_order_acquire);
120*49e08aacStbbdev             call_itt_notify(acquired, &m);
121*49e08aacStbbdev             return true;
122*49e08aacStbbdev         }
123*49e08aacStbbdev 
124*49e08aacStbbdev         //! Release lock.
125*49e08aacStbbdev         void release()
126*49e08aacStbbdev         {
127*49e08aacStbbdev             __TBB_ASSERT(this->m_mutex, "no lock acquired");
128*49e08aacStbbdev 
129*49e08aacStbbdev             call_itt_notify(releasing, this->m_mutex);
130*49e08aacStbbdev 
131*49e08aacStbbdev             if (m_next.load(std::memory_order_relaxed) == nullptr) {
132*49e08aacStbbdev                 scoped_lock* expected = this;
133*49e08aacStbbdev                 if (m_mutex->q_tail.compare_exchange_strong(expected, nullptr)) {
134*49e08aacStbbdev                     // this was the only item in the queue, and the queue is now empty.
135*49e08aacStbbdev                     reset();
136*49e08aacStbbdev                     return;
137*49e08aacStbbdev                 }
138*49e08aacStbbdev                 // Someone in the queue
139*49e08aacStbbdev                 spin_wait_while_eq(m_next, nullptr);
140*49e08aacStbbdev             }
141*49e08aacStbbdev             m_next.load(std::memory_order_relaxed)->m_going.store(1U, std::memory_order_release);
142*49e08aacStbbdev 
143*49e08aacStbbdev             reset();
144*49e08aacStbbdev         }
145*49e08aacStbbdev 
146*49e08aacStbbdev     private:
147*49e08aacStbbdev         //! The pointer to the mutex owned, or NULL if not holding a mutex.
148*49e08aacStbbdev         queuing_mutex* m_mutex{nullptr};
149*49e08aacStbbdev 
150*49e08aacStbbdev         //! The pointer to the next competitor for a mutex
151*49e08aacStbbdev         std::atomic<scoped_lock*> m_next{nullptr};
152*49e08aacStbbdev 
153*49e08aacStbbdev         //! The local spin-wait variable
154*49e08aacStbbdev         /** Inverted (0 - blocked, 1 - acquired the mutex) for the sake of
155*49e08aacStbbdev             zero-initialization.  Defining it as an entire word instead of
156*49e08aacStbbdev             a byte seems to help performance slightly. */
157*49e08aacStbbdev         std::atomic<uintptr_t> m_going{0U};
158*49e08aacStbbdev     };
159*49e08aacStbbdev 
160*49e08aacStbbdev     // Mutex traits
161*49e08aacStbbdev     static constexpr bool is_rw_mutex = false;
162*49e08aacStbbdev     static constexpr bool is_recursive_mutex = false;
163*49e08aacStbbdev     static constexpr bool is_fair_mutex = true;
164*49e08aacStbbdev 
165*49e08aacStbbdev private:
166*49e08aacStbbdev     //! The last competitor requesting the lock
167*49e08aacStbbdev     std::atomic<scoped_lock*> q_tail{nullptr};
168*49e08aacStbbdev 
169*49e08aacStbbdev };
170*49e08aacStbbdev 
171*49e08aacStbbdev #if TBB_USE_PROFILING_TOOLS
172*49e08aacStbbdev inline void set_name(queuing_mutex& obj, const char* name) {
173*49e08aacStbbdev     itt_set_sync_name(&obj, name);
174*49e08aacStbbdev }
175*49e08aacStbbdev #if (_WIN32||_WIN64) && !__MINGW32__
176*49e08aacStbbdev inline void set_name(queuing_mutex& obj, const wchar_t* name) {
177*49e08aacStbbdev     itt_set_sync_name(&obj, name);
178*49e08aacStbbdev }
179*49e08aacStbbdev #endif //WIN
180*49e08aacStbbdev #else
181*49e08aacStbbdev inline void set_name(queuing_mutex&, const char*) {}
182*49e08aacStbbdev #if (_WIN32||_WIN64) && !__MINGW32__
183*49e08aacStbbdev inline void set_name(queuing_mutex&, const wchar_t*) {}
184*49e08aacStbbdev #endif //WIN
185*49e08aacStbbdev #endif
186*49e08aacStbbdev } // namespace d1
187*49e08aacStbbdev } // namespace detail
188*49e08aacStbbdev 
189*49e08aacStbbdev inline namespace v1 {
190*49e08aacStbbdev using detail::d1::queuing_mutex;
191*49e08aacStbbdev } // namespace v1
192*49e08aacStbbdev namespace profiling {
193*49e08aacStbbdev     using detail::d1::set_name;
194*49e08aacStbbdev }
195*49e08aacStbbdev } // namespace tbb
196*49e08aacStbbdev 
197*49e08aacStbbdev #endif /* __TBB_queuing_mutex_H */
198