threading.cpp (15031B)
1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "threading.h" 5 #include "assert.h" 6 #include <memory> 7 8 #if !defined(_WIN32) && !defined(__APPLE__) 9 #ifndef _GNU_SOURCE 10 #define _GNU_SOURCE 11 #endif 12 #endif 13 14 #if defined(_WIN32) 15 #include "windows_headers.h" 16 #include <process.h> 17 #else 18 #include <pthread.h> 19 #include <unistd.h> 20 #if defined(__linux__) 21 #include <sched.h> 22 #include <sys/prctl.h> 23 #include <sys/types.h> 24 25 // glibc < v2.30 doesn't define gettid... 26 #if __GLIBC__ == 2 && __GLIBC_MINOR__ < 30 27 #include <sys/syscall.h> 28 #define gettid() syscall(SYS_gettid) 29 #endif 30 #elif defined(__APPLE__) 31 #include <mach/mach.h> 32 #include <mach/mach_error.h> 33 #include <mach/mach_time.h> 34 #include <mach/semaphore.h> 35 #include <mach/task.h> 36 #else 37 #include <pthread_np.h> 38 #endif 39 #endif 40 41 #ifdef _WIN32 42 union FileTimeU64Union 43 { 44 FILETIME filetime; 45 u64 u64time; 46 }; 47 #endif 48 49 #ifdef __APPLE__ 50 // gets the CPU time used by the current thread (both system and user), in 51 // microseconds, returns 0 on failure 52 static u64 getthreadtime(thread_port_t thread) 53 { 54 mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; 55 thread_basic_info_data_t info; 56 57 kern_return_t kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)&info, &count); 58 if (kr != KERN_SUCCESS) 59 return 0; 60 61 // add system and user time 62 return (u64)info.user_time.seconds * (u64)1e6 + (u64)info.user_time.microseconds + 63 (u64)info.system_time.seconds * (u64)1e6 + (u64)info.system_time.microseconds; 64 } 65 #endif 66 67 #if defined(__linux__) || defined(__FreeBSD__) 68 // Helper function to get either either the current cpu usage 69 // in called thread or in id thread 70 static u64 get_thread_time(void* id = 0) 71 { 72 clockid_t cid; 73 if (id) 74 { 75 int err = pthread_getcpuclockid((pthread_t)id, &cid); 76 if (err) 77 return 0; 78 } 79 else 80 { 81 cid = CLOCK_THREAD_CPUTIME_ID; 82 } 83 84 struct timespec ts; 85 int err = clock_gettime(cid, &ts); 86 if (err) 87 return 0; 88 89 return (u64)ts.tv_sec * (u64)1e6 + (u64)ts.tv_nsec / (u64)1e3; 90 } 91 #endif 92 93 void Threading::Timeslice() 94 { 95 #if defined(_WIN32) 96 ::Sleep(0); 97 #elif defined(__APPLE__) 98 sched_yield(); 99 #else 100 sched_yield(); 101 #endif 102 } 103 104 Threading::ThreadHandle::ThreadHandle() = default; 105 106 #ifdef _WIN32 107 Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle) 108 { 109 if (handle.m_native_handle) 110 { 111 HANDLE new_handle; 112 if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle, 113 THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0)) 114 { 115 m_native_handle = (void*)new_handle; 116 } 117 } 118 } 119 #else 120 Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle) 121 : m_native_handle(handle.m_native_handle) 122 #ifdef __linux__ 123 , 124 m_native_id(handle.m_native_id) 125 #endif 126 { 127 } 128 #endif 129 130 #ifdef _WIN32 131 Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle) : m_native_handle(handle.m_native_handle) 132 { 133 handle.m_native_handle = nullptr; 134 } 135 #else 136 Threading::ThreadHandle::ThreadHandle(ThreadHandle&& handle) 137 : m_native_handle(handle.m_native_handle) 138 #ifdef __linux__ 139 , 140 m_native_id(handle.m_native_id) 141 #endif 142 { 143 handle.m_native_handle = nullptr; 144 #ifdef __linux__ 145 handle.m_native_id = 0; 146 #endif 147 } 148 #endif 149 150 Threading::ThreadHandle::~ThreadHandle() 151 { 152 #ifdef _WIN32 153 if (m_native_handle) 154 CloseHandle(m_native_handle); 155 #endif 156 } 157 158 Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread() 159 { 160 ThreadHandle ret; 161 #ifdef _WIN32 162 ret.m_native_handle = 163 (void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, GetCurrentThreadId()); 164 #else 165 ret.m_native_handle = (void*)pthread_self(); 166 #ifdef __linux__ 167 ret.m_native_id = gettid(); 168 #endif 169 #endif 170 return ret; 171 } 172 173 Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handle) 174 { 175 #ifdef _WIN32 176 if (m_native_handle) 177 CloseHandle((HANDLE)m_native_handle); 178 m_native_handle = handle.m_native_handle; 179 handle.m_native_handle = nullptr; 180 #else 181 m_native_handle = handle.m_native_handle; 182 handle.m_native_handle = nullptr; 183 #ifdef __linux__ 184 m_native_id = handle.m_native_id; 185 handle.m_native_id = 0; 186 #endif 187 #endif 188 return *this; 189 } 190 191 Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle& handle) 192 { 193 #ifdef _WIN32 194 if (m_native_handle) 195 { 196 CloseHandle((HANDLE)m_native_handle); 197 m_native_handle = nullptr; 198 } 199 200 HANDLE new_handle; 201 if (DuplicateHandle(GetCurrentProcess(), (HANDLE)handle.m_native_handle, GetCurrentProcess(), &new_handle, 202 THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0)) 203 { 204 m_native_handle = (void*)new_handle; 205 } 206 #else 207 m_native_handle = handle.m_native_handle; 208 #ifdef __linux__ 209 m_native_id = handle.m_native_id; 210 #endif 211 #endif 212 213 return *this; 214 } 215 216 u64 Threading::ThreadHandle::GetCPUTime() const 217 { 218 #if defined(_WIN32) && !defined(_M_ARM64) 219 u64 ret = 0; 220 if (m_native_handle) 221 QueryThreadCycleTime((HANDLE)m_native_handle, &ret); 222 return ret; 223 #elif defined(_WIN32) 224 FileTimeU64Union user = {}, kernel = {}; 225 FILETIME dummy; 226 GetThreadTimes((HANDLE)m_native_handle, &dummy, &dummy, &kernel.filetime, &user.filetime); 227 return user.u64time + kernel.u64time; 228 #elif defined(__APPLE__) 229 return getthreadtime(pthread_mach_thread_np((pthread_t)m_native_handle)); 230 #elif defined(__linux__) || defined(__FreeBSD__) 231 return get_thread_time(m_native_handle); 232 #else 233 return 0; 234 #endif 235 } 236 237 bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const 238 { 239 #if defined(_WIN32) 240 if (processor_mask == 0) 241 processor_mask = ~processor_mask; 242 243 return (SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)processor_mask) != 0 || GetLastError() != ERROR_SUCCESS); 244 #elif defined(__linux__) 245 cpu_set_t set; 246 CPU_ZERO(&set); 247 248 if (processor_mask != 0) 249 { 250 for (u32 i = 0; i < 64; i++) 251 { 252 if (processor_mask & (static_cast<u64>(1) << i)) 253 { 254 CPU_SET(i, &set); 255 } 256 } 257 } 258 else 259 { 260 long num_processors = sysconf(_SC_NPROCESSORS_CONF); 261 for (long i = 0; i < num_processors; i++) 262 { 263 CPU_SET(i, &set); 264 } 265 } 266 267 return sched_setaffinity((pid_t)m_native_id, sizeof(set), &set) >= 0; 268 #else 269 return false; 270 #endif 271 } 272 273 Threading::Thread::Thread() = default; 274 275 Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread), m_stack_size(thread.m_stack_size) 276 { 277 thread.m_stack_size = 0; 278 } 279 280 Threading::Thread::Thread(EntryPoint func) : ThreadHandle() 281 { 282 if (!Start(std::move(func))) 283 Panic("Failed to start implicitly started thread."); 284 } 285 286 Threading::Thread::~Thread() 287 { 288 AssertMsg(!m_native_handle, "Thread should be detached or joined at destruction"); 289 } 290 291 void Threading::Thread::SetStackSize(u32 size) 292 { 293 AssertMsg(!m_native_handle, "Can't change the stack size on a started thread"); 294 m_stack_size = size; 295 } 296 297 #if defined(_WIN32) 298 299 unsigned Threading::Thread::ThreadProc(void* param) 300 { 301 std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param)); 302 (*entry.get())(); 303 return 0; 304 } 305 306 bool Threading::Thread::Start(EntryPoint func) 307 { 308 AssertMsg(!m_native_handle, "Can't start an already-started thread"); 309 310 std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func))); 311 unsigned thread_id; 312 m_native_handle = 313 reinterpret_cast<void*>(_beginthreadex(nullptr, m_stack_size, ThreadProc, func_clone.get(), 0, &thread_id)); 314 if (!m_native_handle) 315 return false; 316 317 // thread started, it'll release the memory 318 func_clone.release(); 319 return true; 320 } 321 322 #elif defined(__linux__) 323 324 // For Linux, we have to do a bit of trickery here to get the thread's ID back from 325 // the thread itself, because it's not part of pthreads. We use a semaphore to signal 326 // when the thread has started, and filled in thread_id_ptr. 327 struct ThreadProcParameters 328 { 329 Threading::Thread::EntryPoint func; 330 Threading::KernelSemaphore* start_semaphore; 331 unsigned int* thread_id_ptr; 332 }; 333 334 void* Threading::Thread::ThreadProc(void* param) 335 { 336 std::unique_ptr<ThreadProcParameters> entry(static_cast<ThreadProcParameters*>(param)); 337 *entry->thread_id_ptr = gettid(); 338 entry->start_semaphore->Post(); 339 entry->func(); 340 return nullptr; 341 } 342 343 bool Threading::Thread::Start(EntryPoint func) 344 { 345 AssertMsg(!m_native_handle, "Can't start an already-started thread"); 346 347 KernelSemaphore start_semaphore; 348 std::unique_ptr<ThreadProcParameters> params(std::make_unique<ThreadProcParameters>()); 349 params->func = std::move(func); 350 params->start_semaphore = &start_semaphore; 351 params->thread_id_ptr = &m_native_id; 352 353 pthread_attr_t attrs; 354 bool has_attributes = false; 355 356 if (m_stack_size != 0) 357 { 358 has_attributes = true; 359 pthread_attr_init(&attrs); 360 } 361 if (m_stack_size != 0) 362 pthread_attr_setstacksize(&attrs, m_stack_size); 363 364 pthread_t handle; 365 const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, params.get()); 366 if (res != 0) 367 return false; 368 369 // wait until it sets our native id 370 start_semaphore.Wait(); 371 372 // thread started, it'll release the memory 373 m_native_handle = (void*)handle; 374 params.release(); 375 return true; 376 } 377 378 #else 379 380 void* Threading::Thread::ThreadProc(void* param) 381 { 382 std::unique_ptr<EntryPoint> entry(static_cast<EntryPoint*>(param)); 383 (*entry.get())(); 384 return nullptr; 385 } 386 387 bool Threading::Thread::Start(EntryPoint func) 388 { 389 AssertMsg(!m_native_handle, "Can't start an already-started thread"); 390 391 std::unique_ptr<EntryPoint> func_clone(std::make_unique<EntryPoint>(std::move(func))); 392 393 pthread_attr_t attrs; 394 bool has_attributes = false; 395 396 if (m_stack_size != 0) 397 { 398 has_attributes = true; 399 pthread_attr_init(&attrs); 400 } 401 if (m_stack_size != 0) 402 pthread_attr_setstacksize(&attrs, m_stack_size); 403 404 pthread_t handle; 405 const int res = pthread_create(&handle, has_attributes ? &attrs : nullptr, ThreadProc, func_clone.get()); 406 if (res != 0) 407 return false; 408 409 // thread started, it'll release the memory 410 m_native_handle = (void*)handle; 411 func_clone.release(); 412 return true; 413 } 414 415 #endif 416 417 void Threading::Thread::Detach() 418 { 419 AssertMsg(m_native_handle, "Can't detach without a thread"); 420 #ifdef _WIN32 421 CloseHandle((HANDLE)m_native_handle); 422 m_native_handle = nullptr; 423 #else 424 pthread_detach((pthread_t)m_native_handle); 425 m_native_handle = nullptr; 426 #ifdef __linux__ 427 m_native_id = 0; 428 #endif 429 #endif 430 } 431 432 void Threading::Thread::Join() 433 { 434 AssertMsg(m_native_handle, "Can't join without a thread"); 435 #ifdef _WIN32 436 const DWORD res = WaitForSingleObject((HANDLE)m_native_handle, INFINITE); 437 if (res != WAIT_OBJECT_0) 438 Panic("WaitForSingleObject() for thread join failed"); 439 440 CloseHandle((HANDLE)m_native_handle); 441 m_native_handle = nullptr; 442 #else 443 void* retval; 444 const int res = pthread_join((pthread_t)m_native_handle, &retval); 445 if (res != 0) 446 Panic("pthread_join() for thread join failed"); 447 448 m_native_handle = nullptr; 449 #ifdef __linux__ 450 m_native_id = 0; 451 #endif 452 #endif 453 } 454 455 Threading::ThreadHandle& Threading::Thread::operator=(Thread&& thread) 456 { 457 ThreadHandle::operator=(thread); 458 m_stack_size = thread.m_stack_size; 459 thread.m_stack_size = 0; 460 return *this; 461 } 462 463 u64 Threading::GetThreadCpuTime() 464 { 465 #if defined(_WIN32) && !defined(_M_ARM64) 466 u64 ret = 0; 467 QueryThreadCycleTime(GetCurrentThread(), &ret); 468 return ret; 469 #elif defined(_WIN32) 470 FileTimeU64Union user = {}, kernel = {}; 471 FILETIME dummy; 472 GetThreadTimes(GetCurrentThread(), &dummy, &dummy, &kernel.filetime, &user.filetime); 473 return user.u64time + kernel.u64time; 474 #elif defined(__APPLE__) 475 return getthreadtime(pthread_mach_thread_np(pthread_self())); 476 #else 477 return get_thread_time(nullptr); 478 #endif 479 } 480 481 u64 Threading::GetThreadTicksPerSecond() 482 { 483 #if defined(_WIN32) && !defined(_M_ARM64) 484 // On x86, despite what the MS documentation says, this basically appears to be rdtsc. 485 // So, the frequency is our base clock speed (and stable regardless of power management). 486 static u64 frequency = 0; 487 if (frequency == 0) [[unlikely]] 488 { 489 frequency = 1000000; 490 491 HKEY hKey; 492 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", 0, KEY_READ, &hKey) == 493 ERROR_SUCCESS) 494 { 495 DWORD value; 496 DWORD value_size = sizeof(value); 497 if (RegQueryValueExW(hKey, L"~MHz", 0, nullptr, reinterpret_cast<LPBYTE>(&value), &value_size) == ERROR_SUCCESS) 498 { 499 // value is in mhz, convert to hz 500 frequency *= value; 501 } 502 503 RegCloseKey(hKey); 504 } 505 } 506 507 return frequency; 508 #elif defined(_WIN32) 509 return 10000000; 510 #elif defined(__APPLE__) 511 return 1000000; 512 513 #else 514 return 1000000; 515 #endif 516 } 517 518 void Threading::SetNameOfCurrentThread(const char* name) 519 { 520 // This feature needs Windows headers and MSVC's SEH support: 521 522 #if defined(_WIN32) && defined(_MSC_VER) 523 524 // This code sample was borrowed form some obscure MSDN article. 525 // In a rare bout of sanity, it's an actual Microsoft-published hack 526 // that actually works! 527 528 static const int MS_VC_EXCEPTION = 0x406D1388; 529 530 #pragma pack(push, 8) 531 struct THREADNAME_INFO 532 { 533 DWORD dwType; // Must be 0x1000. 534 LPCSTR szName; // Pointer to name (in user addr space). 535 DWORD dwThreadID; // Thread ID (-1=caller thread). 536 DWORD dwFlags; // Reserved for future use, must be zero. 537 }; 538 #pragma pack(pop) 539 540 THREADNAME_INFO info; 541 info.dwType = 0x1000; 542 info.szName = name; 543 info.dwThreadID = GetCurrentThreadId(); 544 info.dwFlags = 0; 545 546 __try 547 { 548 RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); 549 } 550 __except (EXCEPTION_EXECUTE_HANDLER) 551 { 552 } 553 #elif defined(__linux__) 554 // Extract of manpage: "The name can be up to 16 bytes long, and should be 555 // null-terminated if it contains fewer bytes." 556 prctl(PR_SET_NAME, name, 0, 0, 0); 557 #elif defined(__APPLE__) 558 pthread_setname_np(name); 559 #else 560 pthread_set_name_np(pthread_self(), name); 561 #endif 562 } 563 564 Threading::KernelSemaphore::KernelSemaphore() 565 { 566 #ifdef _WIN32 567 m_sema = CreateSemaphore(nullptr, 0, LONG_MAX, nullptr); 568 #elif defined(__APPLE__) 569 semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, 0); 570 #else 571 sem_init(&m_sema, false, 0); 572 #endif 573 } 574 575 Threading::KernelSemaphore::~KernelSemaphore() 576 { 577 #ifdef _WIN32 578 CloseHandle(m_sema); 579 #elif defined(__APPLE__) 580 semaphore_destroy(mach_task_self(), m_sema); 581 #else 582 sem_destroy(&m_sema); 583 #endif 584 } 585 586 void Threading::KernelSemaphore::Post() 587 { 588 #ifdef _WIN32 589 ReleaseSemaphore(m_sema, 1, nullptr); 590 #elif defined(__APPLE__) 591 semaphore_signal(m_sema); 592 #else 593 sem_post(&m_sema); 594 #endif 595 } 596 597 void Threading::KernelSemaphore::Wait() 598 { 599 #ifdef _WIN32 600 WaitForSingleObject(m_sema, INFINITE); 601 #elif defined(__APPLE__) 602 semaphore_wait(m_sema); 603 #else 604 sem_wait(&m_sema); 605 #endif 606 } 607 608 bool Threading::KernelSemaphore::TryWait() 609 { 610 #ifdef _WIN32 611 return WaitForSingleObject(m_sema, 0) == WAIT_OBJECT_0; 612 #elif defined(__APPLE__) 613 mach_timespec_t time = {}; 614 kern_return_t res = semaphore_timedwait(m_sema, time); 615 return (res != KERN_OPERATION_TIMED_OUT); 616 #else 617 return sem_trywait(&m_sema) == 0; 618 #endif 619 }