| 1 | /* |
| 2 | Copyright (c) 2005-2019 Intel Corporation |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | // All platform-specific threading support is encapsulated here. */ |
| 18 | |
| 19 | #ifndef __RML_thread_monitor_H |
| 20 | #define __RML_thread_monitor_H |
| 21 | |
| 22 | #if USE_WINTHREAD |
| 23 | #include <windows.h> |
| 24 | #include <process.h> |
| 25 | #include <malloc.h> //_alloca |
| 26 | #include "tbb/tbb_misc.h" // support for processor groups |
| 27 | #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) |
| 28 | #include <thread> |
| 29 | #endif |
| 30 | #elif USE_PTHREAD |
| 31 | #include <pthread.h> |
| 32 | #include <string.h> |
| 33 | #include <stdlib.h> |
| 34 | #else |
| 35 | #error Unsupported platform |
| 36 | #endif |
| 37 | #include <stdio.h> |
| 38 | #include "tbb/itt_notify.h" |
| 39 | #include "tbb/atomic.h" |
| 40 | #include "tbb/semaphore.h" |
| 41 | |
| 42 | // All platform-specific threading support is in this header. |
| 43 | |
| 44 | #if (_WIN32||_WIN64)&&!__TBB_ipf |
| 45 | // Deal with 64K aliasing. The formula for "offset" is a Fibonacci hash function, |
| 46 | // which has the desirable feature of spreading out the offsets fairly evenly |
| 47 | // without knowing the total number of offsets, and furthermore unlikely to |
| 48 | // accidentally cancel out other 64K aliasing schemes that Microsoft might implement later. |
| 49 | // See Knuth Vol 3. "Theorem S" for details on Fibonacci hashing. |
| 50 | // The second statement is really does need "volatile", otherwise the compiler might remove the _alloca. |
| 51 | #define AVOID_64K_ALIASING(idx) \ |
| 52 | size_t offset = (idx+1) * 40503U % (1U<<16); \ |
| 53 | void* volatile sink_for_alloca = _alloca(offset); \ |
| 54 | __TBB_ASSERT_EX(sink_for_alloca, "_alloca failed"); |
| 55 | #else |
| 56 | // Linux thread allocators avoid 64K aliasing. |
| 57 | #define AVOID_64K_ALIASING(idx) tbb::internal::suppress_unused_warning(idx) |
| 58 | #endif /* _WIN32||_WIN64 */ |
| 59 | |
| 60 | namespace rml { |
| 61 | |
| 62 | namespace internal { |
| 63 | |
| 64 | #if DO_ITT_NOTIFY |
| 65 | static const ::tbb::tchar *SyncType_RML = _T("%Constant" ); |
| 66 | static const ::tbb::tchar *SyncObj_ThreadMonitor = _T("RML Thr Monitor" ); |
| 67 | #endif /* DO_ITT_NOTIFY */ |
| 68 | |
| 69 | //! Monitor with limited two-phase commit form of wait. |
| 70 | /** At most one thread should wait on an instance at a time. */ |
| 71 | class thread_monitor { |
| 72 | public: |
| 73 | class cookie { |
| 74 | friend class thread_monitor; |
| 75 | tbb::atomic<size_t> my_epoch; |
| 76 | }; |
| 77 | thread_monitor() : skipped_wakeup(false), my_sema() { |
| 78 | my_cookie.my_epoch = 0; |
| 79 | ITT_SYNC_CREATE(&my_sema, SyncType_RML, SyncObj_ThreadMonitor); |
| 80 | in_wait = false; |
| 81 | } |
| 82 | ~thread_monitor() {} |
| 83 | |
| 84 | //! If a thread is waiting or started a two-phase wait, notify it. |
| 85 | /** Can be called by any thread. */ |
| 86 | void notify(); |
| 87 | |
| 88 | //! Begin two-phase wait. |
| 89 | /** Should only be called by thread that owns the monitor. |
| 90 | The caller must either complete the wait or cancel it. */ |
| 91 | void prepare_wait( cookie& c ); |
| 92 | |
| 93 | //! Complete a two-phase wait and wait until notification occurs after the earlier prepare_wait. |
| 94 | void commit_wait( cookie& c ); |
| 95 | |
| 96 | //! Cancel a two-phase wait. |
| 97 | void cancel_wait(); |
| 98 | |
| 99 | #if USE_WINTHREAD |
| 100 | typedef HANDLE handle_type; |
| 101 | |
| 102 | #define __RML_DECL_THREAD_ROUTINE unsigned WINAPI |
| 103 | typedef unsigned (WINAPI *thread_routine_type)(void*); |
| 104 | |
| 105 | //! Launch a thread |
| 106 | static handle_type launch( thread_routine_type thread_routine, void* arg, size_t stack_size, const size_t* worker_index = NULL ); |
| 107 | |
| 108 | #elif USE_PTHREAD |
| 109 | typedef pthread_t handle_type; |
| 110 | |
| 111 | #define __RML_DECL_THREAD_ROUTINE void* |
| 112 | typedef void*(*thread_routine_type)(void*); |
| 113 | |
| 114 | //! Launch a thread |
| 115 | static handle_type launch( thread_routine_type thread_routine, void* arg, size_t stack_size ); |
| 116 | #endif /* USE_PTHREAD */ |
| 117 | |
| 118 | //! Yield control to OS |
| 119 | /** Affects the calling thread. **/ |
| 120 | static void yield(); |
| 121 | |
| 122 | //! Join thread |
| 123 | static void join(handle_type handle); |
| 124 | |
| 125 | //! Detach thread |
| 126 | static void detach_thread(handle_type handle); |
| 127 | private: |
| 128 | cookie my_cookie; // epoch counter |
| 129 | tbb::atomic<bool> in_wait; |
| 130 | bool skipped_wakeup; |
| 131 | tbb::internal::binary_semaphore my_sema; |
| 132 | #if USE_PTHREAD |
| 133 | static void check( int error_code, const char* routine ); |
| 134 | #endif |
| 135 | }; |
| 136 | |
| 137 | #if USE_WINTHREAD |
| 138 | |
| 139 | #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION |
| 140 | #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 |
| 141 | #endif |
| 142 | |
| 143 | // _beginthreadex API is not available in Windows 8 Store* applications, so use std::thread instead |
| 144 | #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) |
| 145 | inline thread_monitor::handle_type thread_monitor::launch( thread_routine_type thread_function, void* arg, size_t, const size_t*) { |
| 146 | //TODO: check that exception thrown from std::thread is not swallowed silently |
| 147 | std::thread* thread_tmp=new std::thread(thread_function, arg); |
| 148 | return thread_tmp->native_handle(); |
| 149 | } |
| 150 | #else |
| 151 | inline thread_monitor::handle_type thread_monitor::launch( thread_routine_type thread_routine, void* arg, size_t stack_size, const size_t* worker_index ) { |
| 152 | unsigned thread_id; |
| 153 | int number_of_processor_groups = ( worker_index ) ? tbb::internal::NumberOfProcessorGroups() : 0; |
| 154 | unsigned create_flags = ( number_of_processor_groups > 1 ) ? CREATE_SUSPENDED : 0; |
| 155 | HANDLE h = (HANDLE)_beginthreadex( NULL, unsigned(stack_size), thread_routine, arg, STACK_SIZE_PARAM_IS_A_RESERVATION | create_flags, &thread_id ); |
| 156 | if( !h ) { |
| 157 | fprintf(stderr,"thread_monitor::launch: _beginthreadex failed\n" ); |
| 158 | exit(1); |
| 159 | } |
| 160 | if ( number_of_processor_groups > 1 ) { |
| 161 | tbb::internal::MoveThreadIntoProcessorGroup( h, |
| 162 | tbb::internal::FindProcessorGroupIndex( static_cast<int>(*worker_index) ) ); |
| 163 | ResumeThread( h ); |
| 164 | } |
| 165 | return h; |
| 166 | } |
| 167 | #endif //__TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) |
| 168 | |
| 169 | void thread_monitor::join(handle_type handle) { |
| 170 | #if TBB_USE_ASSERT |
| 171 | DWORD res = |
| 172 | #endif |
| 173 | WaitForSingleObjectEx(handle, INFINITE, FALSE); |
| 174 | __TBB_ASSERT( res==WAIT_OBJECT_0, NULL ); |
| 175 | #if TBB_USE_ASSERT |
| 176 | BOOL val = |
| 177 | #endif |
| 178 | CloseHandle(handle); |
| 179 | __TBB_ASSERT( val, NULL ); |
| 180 | } |
| 181 | |
| 182 | void thread_monitor::detach_thread(handle_type handle) { |
| 183 | #if TBB_USE_ASSERT |
| 184 | BOOL val = |
| 185 | #endif |
| 186 | CloseHandle(handle); |
| 187 | __TBB_ASSERT( val, NULL ); |
| 188 | } |
| 189 | |
| 190 | inline void thread_monitor::yield() { |
| 191 | // TODO: consider unification via __TBB_Yield or tbb::this_tbb_thread::yield |
| 192 | #if __TBB_WIN8UI_SUPPORT && (_WIN32_WINNT < 0x0A00) |
| 193 | std::this_thread::yield(); |
| 194 | #else |
| 195 | SwitchToThread(); |
| 196 | #endif |
| 197 | } |
| 198 | #endif /* USE_WINTHREAD */ |
| 199 | |
| 200 | #if USE_PTHREAD |
| 201 | // TODO: can we throw exceptions instead of termination? |
| 202 | inline void thread_monitor::check( int error_code, const char* routine ) { |
| 203 | if( error_code ) { |
| 204 | fprintf(stderr,"thread_monitor %s in %s\n" , strerror(error_code), routine ); |
| 205 | exit(1); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | inline thread_monitor::handle_type thread_monitor::launch( void* (*thread_routine)(void*), void* arg, size_t stack_size ) { |
| 210 | // FIXME - consider more graceful recovery than just exiting if a thread cannot be launched. |
| 211 | // Note that there are some tricky situations to deal with, such that the thread is already |
| 212 | // grabbed as part of an OpenMP team. |
| 213 | pthread_attr_t s; |
| 214 | check(pthread_attr_init( &s ), "pthread_attr_init" ); |
| 215 | if( stack_size>0 ) |
| 216 | check(pthread_attr_setstacksize( &s, stack_size ), "pthread_attr_setstack_size" ); |
| 217 | pthread_t handle; |
| 218 | check( pthread_create( &handle, &s, thread_routine, arg ), "pthread_create" ); |
| 219 | check( pthread_attr_destroy( &s ), "pthread_attr_destroy" ); |
| 220 | return handle; |
| 221 | } |
| 222 | |
| 223 | void thread_monitor::join(handle_type handle) { |
| 224 | check(pthread_join(handle, NULL), "pthread_join" ); |
| 225 | } |
| 226 | |
| 227 | void thread_monitor::detach_thread(handle_type handle) { |
| 228 | check(pthread_detach(handle), "pthread_detach" ); |
| 229 | } |
| 230 | |
| 231 | inline void thread_monitor::yield() { |
| 232 | sched_yield(); |
| 233 | } |
| 234 | #endif /* USE_PTHREAD */ |
| 235 | |
| 236 | inline void thread_monitor::notify() { |
| 237 | my_cookie.my_epoch = my_cookie.my_epoch + 1; |
| 238 | bool do_signal = in_wait.fetch_and_store( false ); |
| 239 | if( do_signal ) |
| 240 | my_sema.V(); |
| 241 | } |
| 242 | |
| 243 | inline void thread_monitor::prepare_wait( cookie& c ) { |
| 244 | if( skipped_wakeup ) { |
| 245 | // Lazily consume a signal that was skipped due to cancel_wait |
| 246 | skipped_wakeup = false; |
| 247 | my_sema.P(); // does not really wait on the semaphore |
| 248 | } |
| 249 | c = my_cookie; |
| 250 | in_wait.store<tbb::full_fence>( true ); |
| 251 | } |
| 252 | |
| 253 | inline void thread_monitor::commit_wait( cookie& c ) { |
| 254 | bool do_it = ( c.my_epoch == my_cookie.my_epoch ); |
| 255 | if( do_it ) my_sema.P(); |
| 256 | else cancel_wait(); |
| 257 | } |
| 258 | |
| 259 | inline void thread_monitor::cancel_wait() { |
| 260 | // if not in_wait, then some thread has sent us a signal; |
| 261 | // it will be consumed by the next prepare_wait call |
| 262 | skipped_wakeup = ! in_wait.fetch_and_store( false ); |
| 263 | } |
| 264 | |
| 265 | } // namespace internal |
| 266 | } // namespace rml |
| 267 | |
| 268 | #endif /* __RML_thread_monitor_H */ |
| 269 | |