| 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 |  | 
|---|