/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file threadWin32Impl.cxx * @author drose * @date 2006-02-07 */ #include "threadWin32Impl.h" #include "selectThreadImpl.h" #ifdef THREAD_WIN32_IMPL #include "thread.h" #include "pointerTo.h" #include "config_pipeline.h" #include static thread_local Thread *_current_thread = nullptr; static patomic_flag _main_thread_known = ATOMIC_FLAG_INIT; #if _WIN32_WINNT < 0x0601 // Requires Windows 7. static DWORD (__stdcall *EnableThreadProfiling)(HANDLE, DWORD, DWORD64, HANDLE *) = nullptr; static DWORD (__stdcall *DisableThreadProfiling)(HANDLE) = nullptr; static DWORD (__stdcall *ReadThreadProfilingData)(HANDLE, DWORD, PPERFORMANCE_DATA data) = nullptr; static bool init_thread_profiling() { static bool inited = false; if (!inited) { HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); EnableThreadProfiling = (decltype(EnableThreadProfiling))GetProcAddress(kernel32, "EnableThreadProfiling"); DisableThreadProfiling = (decltype(DisableThreadProfiling))GetProcAddress(kernel32, "DisableThreadProfiling"); ReadThreadProfilingData = (decltype(ReadThreadProfilingData))GetProcAddress(kernel32, "ReadThreadProfilingData"); inited = true; } return (EnableThreadProfiling && DisableThreadProfiling && ReadThreadProfilingData); } #else static bool init_thread_profiling() { return true; } #endif /** * Called by get_current_thread() if the current thread pointer is null; checks * whether it might be the main thread. * Note that adding noinline speeds up this call *significantly*, don't remove! */ static __declspec(noinline) Thread * init_current_thread() { Thread *thread = _current_thread; if (!_main_thread_known.test_and_set(std::memory_order_relaxed)) { // Assume that we must be in the main thread, since this method must be // called before the first thread is spawned. thread = Thread::get_main_thread(); _current_thread = thread; } // If this assertion triggers, you are making Panda calls from a thread // that has not first been registered using Thread::bind_thread(). nassertr(thread != nullptr, nullptr); return thread; } /** * */ ThreadWin32Impl:: ~ThreadWin32Impl() { if (thread_cat->is_debug()) { thread_cat.debug() << "Deleting thread " << _parent_obj->get_name() << "\n"; } CloseHandle(_thread); } /** * Called for the main thread only, which has been already started, to fill in * the values appropriate to that thread. */ void ThreadWin32Impl:: setup_main_thread() { _status = S_running; } /** * */ bool ThreadWin32Impl:: start(ThreadPriority priority, bool joinable) { _mutex.lock(); if (thread_cat->is_debug()) { thread_cat.debug() << "Starting " << *_parent_obj << "\n"; } nassertd(_status == S_new && _thread == 0) { _mutex.unlock(); return false; } _joinable = joinable; _status = S_start_called; // Increment the parent object's reference count first. The thread will // eventually decrement it when it terminates. _parent_obj->ref(); _thread = CreateThread(nullptr, 0, &root_func, (void *)this, 0, &_thread_id); if (_thread_id == 0) { // Oops, we couldn't start the thread. Be sure to decrement the reference // count we incremented above, and return false to indicate failure. unref_delete(_parent_obj); _mutex.unlock(); return false; } // Thread was successfully started. Set the priority as specified. switch (priority) { case TP_low: SetThreadPriority(_thread, THREAD_PRIORITY_BELOW_NORMAL); break; case TP_high: SetThreadPriority(_thread, THREAD_PRIORITY_ABOVE_NORMAL); break; case TP_urgent: SetThreadPriority(_thread, THREAD_PRIORITY_HIGHEST); break; case TP_normal: default: SetThreadPriority(_thread, THREAD_PRIORITY_NORMAL); break; } _mutex.unlock(); return true; } /** * Blocks the calling process until the thread terminates. If the thread has * already terminated, this returns immediately. */ void ThreadWin32Impl:: join() { _mutex.lock(); nassertd(_joinable && _status != S_new) { _mutex.unlock(); return; } while (_status != S_finished) { _cv.wait(); } _mutex.unlock(); } /** * */ std::string ThreadWin32Impl:: get_unique_id() const { std::ostringstream strm; strm << GetCurrentProcessId() << "." << _thread_id; return strm.str(); } /** * */ Thread *ThreadWin32Impl:: get_current_thread() { Thread *thread = _current_thread; return (thread != nullptr) ? thread : init_current_thread(); } /** * Associates the indicated Thread object with the currently-executing thread. * You should not call this directly; use Thread::bind_thread() instead. */ void ThreadWin32Impl:: bind_thread(Thread *thread) { if (_current_thread == nullptr && thread == Thread::get_main_thread()) { _main_thread_known.test_and_set(std::memory_order_relaxed); } _current_thread = thread; } /** * Returns the number of context switches that occurred on the current thread. * The first number is the total number of context switches reported by the OS, * and the second number is the number of involuntary context switches (ie. the * thread was scheduled out by the OS), if known, otherwise zero. * Returns true if context switch information was available, false otherwise. */ bool ThreadWin32Impl:: get_context_switches(size_t &total, size_t &involuntary) { Thread *thread = get_current_thread(); ThreadWin32Impl *self = &thread->_impl; if (!self->_profiling && init_thread_profiling()) { DWORD result = EnableThreadProfiling(GetCurrentThread(), THREAD_PROFILING_FLAG_DISPATCH, 0, &self->_profiling); if (result != ERROR_SUCCESS) { self->_profiling = 0; return false; } } PERFORMANCE_DATA data = {sizeof(PERFORMANCE_DATA), PERFORMANCE_DATA_VERSION}; if (ReadThreadProfilingData(self->_profiling, READ_THREAD_PROFILING_FLAG_DISPATCHING, &data) == ERROR_SUCCESS) { total = data.ContextSwitchCount; involuntary = 0; return true; } return false; } /** * The entry point of each thread. */ DWORD ThreadWin32Impl:: root_func(LPVOID data) { TAU_REGISTER_THREAD(); { // TAU_PROFILE("void ThreadWin32Impl::root_func()", " ", TAU_USER); ThreadWin32Impl *self = (ThreadWin32Impl *)data; _current_thread = self->_parent_obj; { self->_mutex.lock(); nassertd(self->_status == S_start_called) { self->_mutex.unlock(); return 1; } self->_status = S_running; self->_cv.notify(); self->_mutex.unlock(); } self->_parent_obj->thread_main(); if (thread_cat->is_debug()) { thread_cat.debug() << "Terminating thread " << self->_parent_obj->get_name() << ", count = " << self->_parent_obj->get_ref_count() << "\n"; } { self->_mutex.lock(); nassertd(self->_status == S_running) { self->_mutex.unlock(); return 1; } self->_status = S_finished; self->_cv.notify(); self->_mutex.unlock(); } if (self->_profiling != 0) { DisableThreadProfiling(self->_profiling); self->_profiling = 0; } // Now drop the parent object reference that we grabbed in start(). This // might delete the parent object, and in turn, delete the ThreadWin32Impl // object. unref_delete(self->_parent_obj); } return 0; } #endif // THREAD_WIN32_IMPL