From 765a834cd04473afeb4b86b445dee901e0d0c83c Mon Sep 17 00:00:00 2001 From: kwolekr Date: Fri, 16 Oct 2015 23:43:29 -0400 Subject: [PATCH] Refactor Thread class to improve readability and portability - Fix some incompatibilities with obscure platforms (AIX and WinCE) - Clean up Thread class interface - Add m_ prefix to private member variables - Simplify platform-dependent logic, reducing preprocessor conditional clauses and improving readibility - Add Thread class documentation --- src/emerge.cpp | 4 +- src/threading/thread.cpp | 390 ++++++++++++++++++++++++--------------- src/threading/thread.h | 161 ++++++++++------ 3 files changed, 350 insertions(+), 205 deletions(-) diff --git a/src/emerge.cpp b/src/emerge.cpp index 21f17cb0e..392a9d81e 100644 --- a/src/emerge.cpp +++ b/src/emerge.cpp @@ -219,7 +219,7 @@ void EmergeManager::initMapgens() Mapgen *EmergeManager::getCurrentMapgen() { for (u32 i = 0; i != m_threads.size(); i++) { - if (m_threads[i]->isSameThread()) + if (m_threads[i]->isCurrentThread()) return m_threads[i]->m_mapgen; } @@ -476,7 +476,7 @@ EmergeThread::EmergeThread(Server *server, int ethreadid) : m_emerge(NULL), m_mapgen(NULL) { - name = "Emerge-" + itos(ethreadid); + m_name = "Emerge-" + itos(ethreadid); } diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp index 1f9b54795..a10082478 100644 --- a/src/threading/thread.cpp +++ b/src/threading/thread.cpp @@ -26,48 +26,48 @@ DEALINGS IN THE SOFTWARE. #include "threading/thread.h" #include "threading/mutex_auto_lock.h" #include "log.h" +#include "porting.h" -#if __cplusplus >= 201103L +#define UNUSED(expr) do { (void)(expr); } while (0) + +#if USE_CPP11_THREADS #include -#else - #define UNUSED(expr) do { (void)(expr); } while (0) -# ifdef _WIN32 -# ifndef _WIN32_WCE - #include -# endif -# else - #include - #include - #include + #include +#elif USE_WIN_THREADS + #ifndef _WIN32_WCE + #include + #endif +#elif USE_POSIX_THREADS + #include + #include + #include + #include #include - // For getNumberOfProcessors - #include -# if defined(__FreeBSD__) || defined(__APPLE__) - #include - #include -# elif defined(_GNU_SOURCE) - #include -# endif -# endif + #if defined(__FreeBSD__) || defined(__APPLE__) + #include + #include + #elif defined(_GNU_SOURCE) + #include + #endif #endif -// For setName +// for setName #if defined(linux) || defined(__linux) #include #elif defined(__FreeBSD__) || defined(__OpenBSD__) #include #elif defined(_MSC_VER) struct THREADNAME_INFO { - DWORD dwType; // Must be 0x1000 - LPCSTR szName; // Pointer to name (in user addr space) + DWORD dwType; // Must be 0x1000 + LPCSTR szName; // Pointer to name (in user addr space) DWORD dwThreadID; // Thread ID (-1=caller thread) - DWORD dwFlags; // Reserved for future use, must be zero + DWORD dwFlags; // Reserved for future use, must be zero }; #endif -// For bindToProcessor +// for bindToProcessor #if __FreeBSD_version >= 702106 typedef cpuset_t cpu_set_t; #elif defined(__linux) || defined(linux) @@ -78,6 +78,7 @@ DEALINGS IN THE SOFTWARE. #include #elif defined(_AIX) #include + #include #elif defined(__APPLE__) #include #include @@ -85,188 +86,246 @@ DEALINGS IN THE SOFTWARE. Thread::Thread(const std::string &name) : - name(name), - retval(NULL), - request_stop(false), - running(false) -#if __cplusplus >= 201103L - , thread(NULL) -#elif !defined(_WIN32) - , started(false) + m_name(name), + m_retval(NULL), + m_request_stop(false), + m_running(false) +{ +#ifdef _AIX + m_kernel_thread_id = -1; #endif -{} +#if USE_CPP11_THREADS + m_thread_obj = NULL; +#endif +} -void Thread::wait() + +Thread::~Thread() { -#if __cplusplus >= 201103L - if (!thread || !thread->joinable()) - return; - thread->join(); -#elif defined(_WIN32) - if (!running) - return; - WaitForSingleObject(thread, INFINITE); -#else // pthread - void *status; - if (!started) - return; - int ret = pthread_join(thread, &status); - assert(!ret); - UNUSED(ret); - started = false; -#endif + kill(); } bool Thread::start() { - if (running) + MutexAutoLock lock(m_continue_mutex); + + if (m_running) return false; - request_stop = false; -#if __cplusplus >= 201103L - MutexAutoLock l(continue_mutex); - thread = new std::thread(theThread, this); -#elif defined(_WIN32) - MutexAutoLock l(continue_mutex); -# ifdef _WIN32_WCE - thread = CreateThread(NULL, 0, theThread, this, 0, &thread_id); -# else - thread = (HANDLE)_beginthreadex(NULL, 0, theThread, this, 0, &thread_id); -# endif - if (!thread) + cleanup(); + +#if USE_CPP11_THREADS + + try { + m_thread_obj = new std::thread(threadProc, this); + m_thread_id = m_thread->get_id(); + m_thread_handle = m_thread->native_handle(); + } except (const std::system_error &e) { return false; -#else - int status; + } - MutexAutoLock l(continue_mutex); +#elif USE_WIN_THREADS - status = pthread_create(&thread, NULL, theThread, this); + m_thread_handle = CreateThread(NULL, 0, threadProc, this, 0, &m_thread_id); + if (!m_thread_handle) + return false; + +#elif USE_POSIX_THREADS + int status = pthread_create(&m_thread_handle, NULL, threadProc, this); if (status) return false; -#endif -#if __cplusplus < 201103L - // Wait until running - while (!running) { -# ifdef _WIN32 - Sleep(1); - } -# else - struct timespec req, rem; - req.tv_sec = 0; - req.tv_nsec = 1000000; - nanosleep(&req, &rem); - } - started = true; -# endif + m_thread_id = m_thread_handle; + #endif + + while (!m_running) + sleep_ms(1); + + return true; +} + + +bool Thread::stop() +{ + m_request_stop = true; return true; } +void Thread::wait() +{ + if (!m_running) + return; + +#if USE_CPP11_THREADS + + m_thread_obj->join(); + +#elif USE_WIN_THREADS + + int ret == WaitForSingleObject(m_thread_handle, INFINITE); + assert(ret == WAIT_OBJECT_0); + UNUSED(ret); + +#elif USE_POSIX_THREADS + + int ret = pthread_join(m_thread_handle, NULL); + assert(ret == 0); + UNUSED(ret); + +#endif + + assert(m_running == false); + + return; +} + + bool Thread::kill() { -#ifdef _WIN32 - if (!running) - return false; - TerminateThread(getThreadHandle(), 0); - CloseHandle(getThreadHandle()); -#else - if (!running) { + if (!m_running) { wait(); return false; } + +#ifdef _WIN32 + TerminateThread(m_thread_handle, 0); +#else + + // We need to pthread_kill instead on Android since NDKv5's pthread + // implementation is incomplete. # ifdef __ANDROID__ - pthread_kill(getThreadHandle(), SIGKILL); + pthread_kill(m_thread_handle, SIGKILL); # else - pthread_cancel(getThreadHandle()); + pthread_cancel(m_thread_handle); # endif + wait(); #endif -#if __cplusplus >= 201103L - delete thread; -#endif - running = false; + + cleanup(); + return true; } -bool Thread::isSameThread() +void Thread::cleanup() { -#if __cplusplus >= 201103L - return thread->get_id() == std::this_thread::get_id(); -#elif defined(_WIN32) - return GetCurrentThreadId() == thread_id; -#else - return pthread_equal(pthread_self(), thread); +#if USE_CPP11_THREADS + + delete m_thread_obj; + m_thread_obj = NULL; + +#elif USE_WIN_THREADS + + CloseHandle(m_thread_handle); + m_thread_handle = NULL; + m_thread_id = -1; + +#elif USE_POSIX_THREADS + + // Can't do any cleanup for pthreads + #endif + + m_name = ""; + m_retval = NULL; + m_running = false; + m_request_stop = false; } -#if __cplusplus >= 201103L -void Thread::theThread(Thread *th) +bool Thread::getReturnValue(void **ret) +{ + if (m_running) + return false; + + *ret = m_retval; + return true; +} + + +bool Thread::isCurrentThread() +{ + return thr_is_current_thread(m_thread_id); +} + + +#if USE_CPP11_THREADS || USE_POSIX_THREADS + void *(Thread::threadProc)(void *param) #elif defined(_WIN32_WCE) -DWORD WINAPI Thread::theThread(void *param) + DWORD (Thread::threadProc)(LPVOID param) #elif defined(_WIN32) -UINT __stdcall Thread::theThread(void *param) -#else -void *Thread::theThread(void *param) + DWORD WINAPI (Thread::threadProc)(LPVOID param) #endif { -#if __cplusplus < 201103L - Thread *th = static_cast(param); + Thread *thr = (Thread *)param; + +#ifdef _AIX + m_kernel_thread_id = thread_self(); #endif - th->running = true; - th->setName(); - g_logger.registerThread(th->name); + thr->setName(thr->m_name); + + g_logger.registerThread(thr->m_name); + thr->m_running = true; - th->retval = th->run(); + thr->m_retval = thr->run(); + thr->m_running = false; g_logger.deregisterThread(); - th->running = false; -#if __cplusplus < 201103L -# ifdef _WIN32 - CloseHandle(th->thread); -# endif return NULL; -#endif } void Thread::setName(const std::string &name) { #if defined(linux) || defined(__linux) - /* It would be cleaner to do this with pthread_setname_np, - * which was added to glibc in version 2.12, but some major - * distributions are still runing 2.11 and previous versions. - */ + + // It would be cleaner to do this with pthread_setname_np, + // which was added to glibc in version 2.12, but some major + // distributions are still runing 2.11 and previous versions. prctl(PR_SET_NAME, name.c_str()); + #elif defined(__FreeBSD__) || defined(__OpenBSD__) + pthread_set_name_np(pthread_self(), name.c_str()); + #elif defined(__NetBSD__) + pthread_setname_np(pthread_self(), name.c_str()); + #elif defined(__APPLE__) + pthread_setname_np(name.c_str()); + #elif defined(_MSC_VER) + // Windows itself doesn't support thread names, // but the MSVC debugger does... THREADNAME_INFO info; + info.dwType = 0x1000; info.szName = name.c_str(); info.dwThreadID = -1; info.dwFlags = 0; + __try { - RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); + RaiseException(0x406D1388, 0, + sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); } __except (EXCEPTION_CONTINUE_EXECUTION) { } + #elif defined(_WIN32) || defined(__GNU__) + // These platforms are known to not support thread names. // Silently ignore the request. + #else #warning "Unrecognized platform, thread names will not be available." #endif @@ -276,59 +335,96 @@ void Thread::setName(const std::string &name) unsigned int Thread::getNumberOfProcessors() { #if __cplusplus >= 201103L + return std::thread::hardware_concurrency(); + #elif defined(_SC_NPROCESSORS_ONLN) + return sysconf(_SC_NPROCESSORS_ONLN); -#elif defined(__FreeBSD__) || defined(__APPLE__) - unsigned int len, count; - len = sizeof(count); - return sysctlbyname("hw.ncpu", &count, &len, NULL, 0); + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) || defined(__APPLE__) + + unsigned int num_cpus = 1; + size_t len = sizeof(num_cpus); + + int mib[2]; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + + sysctl(mib, 2, &num_cpus, &len, NULL, 0); + return num_cpus; + #elif defined(_GNU_SOURCE) + return get_nprocs(); + #elif defined(_WIN32) + SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); return sysinfo.dwNumberOfProcessors; + #elif defined(PTW32_VERSION) || defined(__hpux) + return pthread_num_processors_np(); + #else + return 1; + #endif } -bool Thread::bindToProcessor(unsigned int num) +bool Thread::bindToProcessor(unsigned int proc_number) { #if defined(__ANDROID__) + return false; + #elif defined(_WIN32) - return SetThreadAffinityMask(getThreadHandle(), 1 << num); + + return SetThreadAffinityMask(m_thread_handle, 1 << proc_number); + #elif __FreeBSD_version >= 702106 || defined(__linux) || defined(linux) + cpu_set_t cpuset; + CPU_ZERO(&cpuset); - CPU_SET(num, &cpuset); - return pthread_setaffinity_np(getThreadHandle(), sizeof(cpuset), - &cpuset) == 0; + CPU_SET(proc_number, &cpuset); + + return pthread_setaffinity_np(m_thread_handle, sizeof(cpuset), &cpuset) == 0; + #elif defined(__sun) || defined(sun) - return processor_bind(P_LWPID, MAKE_LWPID_PTHREAD(getThreadHandle()), - num, NULL) == 0 + + return processor_bind(P_LWPID, P_MYID, proc_number, NULL) == 0 + #elif defined(_AIX) - return bindprocessor(BINDTHREAD, (tid_t) getThreadHandle(), pnumber) == 0; + + return bindprocessor(BINDTHREAD, m_kernel_thread_id, proc_number) == 0; + #elif defined(__hpux) || defined(hpux) + pthread_spu_t answer; return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP, - &answer, num, getThreadHandle()) == 0; + &answer, proc_number, m_thread_handle) == 0; + #elif defined(__APPLE__) + struct thread_affinity_policy tapol; - thread_port_t threadport = pthread_mach_thread_np(getThreadHandle()); - tapol.affinity_tag = num + 1; + thread_port_t threadport = pthread_mach_thread_np(m_thread_handle); + tapol.affinity_tag = proc_number + 1; return thread_policy_set(threadport, THREAD_AFFINITY_POLICY, (thread_policy_t)&tapol, THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS; + #else + return false; + #endif } @@ -336,19 +432,23 @@ bool Thread::bindToProcessor(unsigned int num) bool Thread::setPriority(int prio) { #if defined(_WIN32) - return SetThreadPriority(getThreadHandle(), prio); + + return SetThreadPriority(m_thread_handle, prio); + #else + struct sched_param sparam; int policy; - if (pthread_getschedparam(getThreadHandle(), &policy, &sparam) != 0) + if (pthread_getschedparam(m_thread_handle, &policy, &sparam) != 0) return false; int min = sched_get_priority_min(policy); int max = sched_get_priority_max(policy); sparam.sched_priority = min + prio * (max - min) / THREAD_PRIORITY_HIGHEST; - return pthread_setschedparam(getThreadHandle(), policy, &sparam) == 0; + return pthread_setschedparam(m_thread_handle, policy, &sparam) == 0; + #endif } diff --git a/src/threading/thread.h b/src/threading/thread.h index 275bc9b6d..3c8447b53 100644 --- a/src/threading/thread.h +++ b/src/threading/thread.h @@ -28,91 +28,136 @@ DEALINGS IN THE SOFTWARE. #include "threading/atomic.h" #include "threading/mutex.h" +#include "threads.h" #include -#if __cplusplus >= 201103L +#if USE_CPP11_THREADS #include #endif -#ifndef _WIN32 -enum { - THREAD_PRIORITY_LOWEST, - THREAD_PRIORITY_BELOW_NORMAL, - THREAD_PRIORITY_NORMAL, - THREAD_PRIORITY_ABOVE_NORMAL, - THREAD_PRIORITY_HIGHEST, -}; +/* + * On platforms using pthreads, these five priority classes correlate to + * even divisions between the minimum and maximum reported thread priority. + */ +#if !defined(_WIN32) + #define THREAD_PRIORITY_LOWEST 0 + #define THREAD_PRIORITY_BELOW_NORMAL 1 + #define THREAD_PRIORITY_NORMAL 2 + #define THREAD_PRIORITY_ABOVE_NORMAL 3 + #define THREAD_PRIORITY_HIGHEST 4 #endif -class Thread -{ +class Thread { public: - Thread(const std::string &name="Unnamed"); - virtual ~Thread() { kill(); } + Thread(const std::string &name=""); + virtual ~Thread(); + /* + * Begins execution of a new thread at the pure virtual method Thread::run(). + * Execution of the thread is guaranteed to have started after this function + * returns. + */ bool start(); - inline void stop() { request_stop = true; } - bool kill(); - inline bool isRunning() { return running; } - inline bool stopRequested() { return request_stop; } - void *getReturnValue() { return running ? NULL : retval; } - bool isSameThread(); + /* + * Requests that the thread exit gracefully. + * Returns immediately; thread execution is guaranteed to be complete after + * a subsequent call to Thread::wait. + */ + bool stop(); - static unsigned int getNumberOfProcessors(); - bool bindToProcessor(unsigned int); - bool setPriority(int); + /* + * Immediately terminates the thread. + * This should be used with extreme caution, as the thread will not have + * any opportunity to release resources it may be holding (such as memory + * or locks). + */ + bool kill(); /* - * Wait for thread to finish. - * Note: this does not stop a thread, you have to do this on your own. + * Waits for thread to finish. + * Note: This does not stop a thread, you have to do this on your own. * Returns immediately if the thread is not started. */ void wait(); + /* + * Returns true if the calling thread is this Thread object. + */ + bool isCurrentThread(); + + inline bool isRunning() { return m_running; } + inline bool stopRequested() { return m_request_stop; } + inline threadid_t getThreadId() { return m_thread_id; } + inline threadhandle_t getThreadHandle() { return m_thread_handle; } + + /* + * Gets the thread return value. + * Returns true if the thread has exited and the return value was available, + * or false if the thread has yet to finish. + */ + bool getReturnValue(void **ret); + + /* + * Binds (if possible, otherwise sets the affinity of) the thread to the + * specific processor specified by proc_number. + */ + bool bindToProcessor(unsigned int proc_number); + + /* + * Sets the thread priority to the specified priority. + * + * prio can be one of: THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL, + * THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST. + * On Windows, any of the other priorites as defined by SetThreadPriority + * are supported as well. + * + * Note that it may be necessary to first set the threading policy or + * scheduling algorithm to one that supports thread priorities if not + * supported by default, otherwise this call will have no effect. + */ + bool setPriority(int prio); + + /* + * Sets the currently executing thread's name to where supported; useful + * for debugging. + */ static void setName(const std::string &name); + /* + * Returns the number of processors/cores configured and active on this machine. + */ + static unsigned int getNumberOfProcessors(); + protected: - std::string name; + std::string m_name; virtual void *run() = 0; private: - void setName() { setName(name); } - - void *retval; - Atomic request_stop; - Atomic running; - Mutex continue_mutex; - -#if __cplusplus >= 201103L - static void theThread(Thread *th); - - std::thread *thread; - std::thread::native_handle_type getThreadHandle() const - { return thread->native_handle(); } -#else -# if defined(WIN32) || defined(_WIN32_WCE) -# ifdef _WIN32_WCE - DWORD thread_id; - static DWORD WINAPI theThread(void *param); -# else - UINT thread_id; - static UINT __stdcall theThread(void *param); -# endif - - HANDLE thread; - HANDLE getThreadHandle() const { return thread; } -# else // pthread - static void *theThread(void *param); - - pthread_t thread; - pthread_t getThreadHandle() const { return thread; } - - Atomic started; -# endif + void *m_retval; + Atomic m_request_stop; + Atomic m_running; + Mutex m_continue_mutex; + + threadid_t m_thread_id; + threadhandle_t m_thread_handle; + + void cleanup(); + + static ThreadStartFunc threadProc; + +#ifdef _AIX + // For AIX, there does not exist any mapping from pthread_t to tid_t + // available to us, so we maintain one ourselves. This is set on thread start. + tid_t m_kernel_thread_id; +#endif + +#if USE_CPP11_THREADS + std::thread *m_thread_obj; #endif + }; #endif -- 2.25.1