threads_win32.h revision 01e04c3f
1/* 2 * C11 <threads.h> emulation library 3 * 4 * (C) Copyright yohhoy 2012. 5 * Distributed under the Boost Software License, Version 1.0. 6 * 7 * Permission is hereby granted, free of charge, to any person or organization 8 * obtaining a copy of the software and accompanying documentation covered by 9 * this license (the "Software") to use, reproduce, display, distribute, 10 * execute, and transmit the Software, and to prepare [[derivative work]]s of the 11 * Software, and to permit third-parties to whom the Software is furnished to 12 * do so, all subject to the following: 13 * 14 * The copyright notices in the Software and this entire statement, including 15 * the above license grant, this restriction and the following disclaimer, 16 * must be included in all copies of the Software, in whole or in part, and 17 * all derivative works of the Software, unless such copies or derivative 18 * works are solely in the form of machine-executable object code generated by 19 * a source language processor. 20 * 21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 24 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 25 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 26 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 * DEALINGS IN THE SOFTWARE. 28 */ 29#ifndef assert 30#include <assert.h> 31#endif 32#include <limits.h> 33#include <errno.h> 34#include <process.h> // MSVCRT 35#include <stdlib.h> 36 37/* 38Configuration macro: 39 40 EMULATED_THREADS_USE_NATIVE_CALL_ONCE 41 Use native WindowsAPI one-time initialization function. 42 (requires WinVista or later) 43 Otherwise emulate by mtx_trylock() + *busy loop* for WinXP. 44 45 EMULATED_THREADS_USE_NATIVE_CV 46 Use native WindowsAPI condition variable object. 47 (requires WinVista or later) 48 Otherwise use emulated implementation for WinXP. 49 50 EMULATED_THREADS_TSS_DTOR_SLOTNUM 51 Max registerable TSS dtor number. 52*/ 53 54// XXX: Retain XP compatability 55#if 0 56#if _WIN32_WINNT >= 0x0600 57// Prefer native WindowsAPI on newer environment. 58#if !defined(__MINGW32__) 59#define EMULATED_THREADS_USE_NATIVE_CALL_ONCE 60#endif 61#define EMULATED_THREADS_USE_NATIVE_CV 62#endif 63#endif 64#define EMULATED_THREADS_TSS_DTOR_SLOTNUM 64 // see TLS_MINIMUM_AVAILABLE 65 66 67#include <windows.h> 68 69// check configuration 70#if defined(EMULATED_THREADS_USE_NATIVE_CALL_ONCE) && (_WIN32_WINNT < 0x0600) 71#error EMULATED_THREADS_USE_NATIVE_CALL_ONCE requires _WIN32_WINNT>=0x0600 72#endif 73 74#if defined(EMULATED_THREADS_USE_NATIVE_CV) && (_WIN32_WINNT < 0x0600) 75#error EMULATED_THREADS_USE_NATIVE_CV requires _WIN32_WINNT>=0x0600 76#endif 77 78/* Visual Studio 2015 and later */ 79#ifdef _MSC_VER 80#define HAVE_TIMESPEC_GET 81#endif 82 83/*---------------------------- macros ----------------------------*/ 84#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE 85#define ONCE_FLAG_INIT INIT_ONCE_STATIC_INIT 86#else 87#define ONCE_FLAG_INIT {0} 88#endif 89#define TSS_DTOR_ITERATIONS 1 90 91// FIXME: temporary non-standard hack to ease transition 92#define _MTX_INITIALIZER_NP {(PCRITICAL_SECTION_DEBUG)-1, -1, 0, 0, 0, 0} 93 94/*---------------------------- types ----------------------------*/ 95typedef struct cnd_t { 96#ifdef EMULATED_THREADS_USE_NATIVE_CV 97 CONDITION_VARIABLE condvar; 98#else 99 int blocked; 100 int gone; 101 int to_unblock; 102 HANDLE sem_queue; 103 HANDLE sem_gate; 104 CRITICAL_SECTION monitor; 105#endif 106} cnd_t; 107 108typedef HANDLE thrd_t; 109 110typedef DWORD tss_t; 111 112typedef CRITICAL_SECTION mtx_t; 113 114#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE 115typedef INIT_ONCE once_flag; 116#else 117typedef struct once_flag_t { 118 volatile LONG status; 119} once_flag; 120#endif 121 122 123static inline void * tss_get(tss_t key); 124static inline void thrd_yield(void); 125static inline int mtx_trylock(mtx_t *mtx); 126static inline int mtx_lock(mtx_t *mtx); 127static inline int mtx_unlock(mtx_t *mtx); 128 129/* 130Implementation limits: 131 - Conditionally emulation for "Initialization functions" 132 (see EMULATED_THREADS_USE_NATIVE_CALL_ONCE macro) 133 - Emulated `mtx_timelock()' with mtx_trylock() + *busy loop* 134*/ 135static void impl_tss_dtor_invoke(void); // forward decl. 136 137struct impl_thrd_param { 138 thrd_start_t func; 139 void *arg; 140}; 141 142static unsigned __stdcall impl_thrd_routine(void *p) 143{ 144 struct impl_thrd_param pack; 145 int code; 146 memcpy(&pack, p, sizeof(struct impl_thrd_param)); 147 free(p); 148 code = pack.func(pack.arg); 149 impl_tss_dtor_invoke(); 150 return (unsigned)code; 151} 152 153static DWORD impl_timespec2msec(const struct timespec *ts) 154{ 155 return (DWORD)((ts->tv_sec * 1000U) + (ts->tv_nsec / 1000000L)); 156} 157 158#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE 159struct impl_call_once_param { void (*func)(void); }; 160static BOOL CALLBACK impl_call_once_callback(PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) 161{ 162 struct impl_call_once_param *param = (struct impl_call_once_param*)Parameter; 163 (param->func)(); 164 ((void)InitOnce); ((void)Context); // suppress warning 165 return TRUE; 166} 167#endif // ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE 168 169#ifndef EMULATED_THREADS_USE_NATIVE_CV 170/* 171Note: 172 The implementation of condition variable is ported from Boost.Interprocess 173 See http://www.boost.org/boost/interprocess/sync/windows/condition.hpp 174*/ 175static void impl_cond_do_signal(cnd_t *cond, int broadcast) 176{ 177 int nsignal = 0; 178 179 EnterCriticalSection(&cond->monitor); 180 if (cond->to_unblock != 0) { 181 if (cond->blocked == 0) { 182 LeaveCriticalSection(&cond->monitor); 183 return; 184 } 185 if (broadcast) { 186 cond->to_unblock += nsignal = cond->blocked; 187 cond->blocked = 0; 188 } else { 189 nsignal = 1; 190 cond->to_unblock++; 191 cond->blocked--; 192 } 193 } else if (cond->blocked > cond->gone) { 194 WaitForSingleObject(cond->sem_gate, INFINITE); 195 if (cond->gone != 0) { 196 cond->blocked -= cond->gone; 197 cond->gone = 0; 198 } 199 if (broadcast) { 200 nsignal = cond->to_unblock = cond->blocked; 201 cond->blocked = 0; 202 } else { 203 nsignal = cond->to_unblock = 1; 204 cond->blocked--; 205 } 206 } 207 LeaveCriticalSection(&cond->monitor); 208 209 if (0 < nsignal) 210 ReleaseSemaphore(cond->sem_queue, nsignal, NULL); 211} 212 213static int impl_cond_do_wait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts) 214{ 215 int nleft = 0; 216 int ngone = 0; 217 int timeout = 0; 218 DWORD w; 219 220 WaitForSingleObject(cond->sem_gate, INFINITE); 221 cond->blocked++; 222 ReleaseSemaphore(cond->sem_gate, 1, NULL); 223 224 mtx_unlock(mtx); 225 226 w = WaitForSingleObject(cond->sem_queue, ts ? impl_timespec2msec(ts) : INFINITE); 227 timeout = (w == WAIT_TIMEOUT); 228 229 EnterCriticalSection(&cond->monitor); 230 if ((nleft = cond->to_unblock) != 0) { 231 if (timeout) { 232 if (cond->blocked != 0) { 233 cond->blocked--; 234 } else { 235 cond->gone++; 236 } 237 } 238 if (--cond->to_unblock == 0) { 239 if (cond->blocked != 0) { 240 ReleaseSemaphore(cond->sem_gate, 1, NULL); 241 nleft = 0; 242 } 243 else if ((ngone = cond->gone) != 0) { 244 cond->gone = 0; 245 } 246 } 247 } else if (++cond->gone == INT_MAX/2) { 248 WaitForSingleObject(cond->sem_gate, INFINITE); 249 cond->blocked -= cond->gone; 250 ReleaseSemaphore(cond->sem_gate, 1, NULL); 251 cond->gone = 0; 252 } 253 LeaveCriticalSection(&cond->monitor); 254 255 if (nleft == 1) { 256 while (ngone--) 257 WaitForSingleObject(cond->sem_queue, INFINITE); 258 ReleaseSemaphore(cond->sem_gate, 1, NULL); 259 } 260 261 mtx_lock(mtx); 262 return timeout ? thrd_busy : thrd_success; 263} 264#endif // ifndef EMULATED_THREADS_USE_NATIVE_CV 265 266static struct impl_tss_dtor_entry { 267 tss_t key; 268 tss_dtor_t dtor; 269} impl_tss_dtor_tbl[EMULATED_THREADS_TSS_DTOR_SLOTNUM]; 270 271static int impl_tss_dtor_register(tss_t key, tss_dtor_t dtor) 272{ 273 int i; 274 for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { 275 if (!impl_tss_dtor_tbl[i].dtor) 276 break; 277 } 278 if (i == EMULATED_THREADS_TSS_DTOR_SLOTNUM) 279 return 1; 280 impl_tss_dtor_tbl[i].key = key; 281 impl_tss_dtor_tbl[i].dtor = dtor; 282 return 0; 283} 284 285static void impl_tss_dtor_invoke() 286{ 287 int i; 288 for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { 289 if (impl_tss_dtor_tbl[i].dtor) { 290 void* val = tss_get(impl_tss_dtor_tbl[i].key); 291 if (val) 292 (impl_tss_dtor_tbl[i].dtor)(val); 293 } 294 } 295} 296 297 298/*--------------- 7.25.2 Initialization functions ---------------*/ 299// 7.25.2.1 300static inline void 301call_once(once_flag *flag, void (*func)(void)) 302{ 303 assert(flag && func); 304#ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE 305 { 306 struct impl_call_once_param param; 307 param.func = func; 308 InitOnceExecuteOnce(flag, impl_call_once_callback, (PVOID)¶m, NULL); 309 } 310#else 311 if (InterlockedCompareExchange(&flag->status, 1, 0) == 0) { 312 (func)(); 313 InterlockedExchange(&flag->status, 2); 314 } else { 315 while (flag->status == 1) { 316 // busy loop! 317 thrd_yield(); 318 } 319 } 320#endif 321} 322 323 324/*------------- 7.25.3 Condition variable functions -------------*/ 325// 7.25.3.1 326static inline int 327cnd_broadcast(cnd_t *cond) 328{ 329 if (!cond) return thrd_error; 330#ifdef EMULATED_THREADS_USE_NATIVE_CV 331 WakeAllConditionVariable(&cond->condvar); 332#else 333 impl_cond_do_signal(cond, 1); 334#endif 335 return thrd_success; 336} 337 338// 7.25.3.2 339static inline void 340cnd_destroy(cnd_t *cond) 341{ 342 assert(cond); 343#ifdef EMULATED_THREADS_USE_NATIVE_CV 344 // do nothing 345#else 346 CloseHandle(cond->sem_queue); 347 CloseHandle(cond->sem_gate); 348 DeleteCriticalSection(&cond->monitor); 349#endif 350} 351 352// 7.25.3.3 353static inline int 354cnd_init(cnd_t *cond) 355{ 356 if (!cond) return thrd_error; 357#ifdef EMULATED_THREADS_USE_NATIVE_CV 358 InitializeConditionVariable(&cond->condvar); 359#else 360 cond->blocked = 0; 361 cond->gone = 0; 362 cond->to_unblock = 0; 363 cond->sem_queue = CreateSemaphore(NULL, 0, LONG_MAX, NULL); 364 cond->sem_gate = CreateSemaphore(NULL, 1, 1, NULL); 365 InitializeCriticalSection(&cond->monitor); 366#endif 367 return thrd_success; 368} 369 370// 7.25.3.4 371static inline int 372cnd_signal(cnd_t *cond) 373{ 374 if (!cond) return thrd_error; 375#ifdef EMULATED_THREADS_USE_NATIVE_CV 376 WakeConditionVariable(&cond->condvar); 377#else 378 impl_cond_do_signal(cond, 0); 379#endif 380 return thrd_success; 381} 382 383// 7.25.3.5 384static inline int 385cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *abs_time) 386{ 387 if (!cond || !mtx || !abs_time) return thrd_error; 388#ifdef EMULATED_THREADS_USE_NATIVE_CV 389 if (SleepConditionVariableCS(&cond->condvar, mtx, impl_timespec2msec(abs_time))) 390 return thrd_success; 391 return (GetLastError() == ERROR_TIMEOUT) ? thrd_busy : thrd_error; 392#else 393 return impl_cond_do_wait(cond, mtx, abs_time); 394#endif 395} 396 397// 7.25.3.6 398static inline int 399cnd_wait(cnd_t *cond, mtx_t *mtx) 400{ 401 if (!cond || !mtx) return thrd_error; 402#ifdef EMULATED_THREADS_USE_NATIVE_CV 403 SleepConditionVariableCS(&cond->condvar, mtx, INFINITE); 404#else 405 impl_cond_do_wait(cond, mtx, NULL); 406#endif 407 return thrd_success; 408} 409 410 411/*-------------------- 7.25.4 Mutex functions --------------------*/ 412// 7.25.4.1 413static inline void 414mtx_destroy(mtx_t *mtx) 415{ 416 assert(mtx); 417 DeleteCriticalSection(mtx); 418} 419 420// 7.25.4.2 421static inline int 422mtx_init(mtx_t *mtx, int type) 423{ 424 if (!mtx) return thrd_error; 425 if (type != mtx_plain && type != mtx_timed && type != mtx_try 426 && type != (mtx_plain|mtx_recursive) 427 && type != (mtx_timed|mtx_recursive) 428 && type != (mtx_try|mtx_recursive)) 429 return thrd_error; 430 InitializeCriticalSection(mtx); 431 return thrd_success; 432} 433 434// 7.25.4.3 435static inline int 436mtx_lock(mtx_t *mtx) 437{ 438 if (!mtx) return thrd_error; 439 EnterCriticalSection(mtx); 440 return thrd_success; 441} 442 443// 7.25.4.4 444static inline int 445mtx_timedlock(mtx_t *mtx, const struct timespec *ts) 446{ 447 time_t expire, now; 448 if (!mtx || !ts) return thrd_error; 449 expire = time(NULL); 450 expire += ts->tv_sec; 451 while (mtx_trylock(mtx) != thrd_success) { 452 now = time(NULL); 453 if (expire < now) 454 return thrd_busy; 455 // busy loop! 456 thrd_yield(); 457 } 458 return thrd_success; 459} 460 461// 7.25.4.5 462static inline int 463mtx_trylock(mtx_t *mtx) 464{ 465 if (!mtx) return thrd_error; 466 return TryEnterCriticalSection(mtx) ? thrd_success : thrd_busy; 467} 468 469// 7.25.4.6 470static inline int 471mtx_unlock(mtx_t *mtx) 472{ 473 if (!mtx) return thrd_error; 474 LeaveCriticalSection(mtx); 475 return thrd_success; 476} 477 478 479/*------------------- 7.25.5 Thread functions -------------------*/ 480// 7.25.5.1 481static inline int 482thrd_create(thrd_t *thr, thrd_start_t func, void *arg) 483{ 484 struct impl_thrd_param *pack; 485 uintptr_t handle; 486 if (!thr) return thrd_error; 487 pack = (struct impl_thrd_param *)malloc(sizeof(struct impl_thrd_param)); 488 if (!pack) return thrd_nomem; 489 pack->func = func; 490 pack->arg = arg; 491 handle = _beginthreadex(NULL, 0, impl_thrd_routine, pack, 0, NULL); 492 if (handle == 0) { 493 if (errno == EAGAIN || errno == EACCES) 494 return thrd_nomem; 495 return thrd_error; 496 } 497 *thr = (thrd_t)handle; 498 return thrd_success; 499} 500 501#if 0 502// 7.25.5.2 503static inline thrd_t 504thrd_current(void) 505{ 506 HANDLE hCurrentThread; 507 BOOL bRet; 508 509 /* GetCurrentThread() returns a pseudo-handle, which we need 510 * to pass to DuplicateHandle(). Only the resulting handle can be used 511 * from other threads. 512 * 513 * Note that neither handle can be compared to the one by thread_create. 514 * Only the thread IDs - as returned by GetThreadId() and GetCurrentThreadId() 515 * can be compared directly. 516 * 517 * Other potential solutions would be: 518 * - define thrd_t as a thread Ids, but this would mean we'd need to OpenThread for many operations 519 * - use malloc'ed memory for thrd_t. This would imply using TLS for current thread. 520 * 521 * Neither is particularly nice. 522 * 523 * Life would be much easier if C11 threads had different abstractions for 524 * threads and thread IDs, just like C++11 threads does... 525 */ 526 527 bRet = DuplicateHandle(GetCurrentProcess(), // source process (pseudo) handle 528 GetCurrentThread(), // source (pseudo) handle 529 GetCurrentProcess(), // target process 530 &hCurrentThread, // target handle 531 0, 532 FALSE, 533 DUPLICATE_SAME_ACCESS); 534 assert(bRet); 535 if (!bRet) { 536 hCurrentThread = GetCurrentThread(); 537 } 538 return hCurrentThread; 539} 540#endif 541 542// 7.25.5.3 543static inline int 544thrd_detach(thrd_t thr) 545{ 546 CloseHandle(thr); 547 return thrd_success; 548} 549 550// 7.25.5.4 551static inline int 552thrd_equal(thrd_t thr0, thrd_t thr1) 553{ 554 return GetThreadId(thr0) == GetThreadId(thr1); 555} 556 557// 7.25.5.5 558static inline void 559thrd_exit(int res) 560{ 561 impl_tss_dtor_invoke(); 562 _endthreadex((unsigned)res); 563} 564 565// 7.25.5.6 566static inline int 567thrd_join(thrd_t thr, int *res) 568{ 569 DWORD w, code; 570 w = WaitForSingleObject(thr, INFINITE); 571 if (w != WAIT_OBJECT_0) 572 return thrd_error; 573 if (res) { 574 if (!GetExitCodeThread(thr, &code)) { 575 CloseHandle(thr); 576 return thrd_error; 577 } 578 *res = (int)code; 579 } 580 CloseHandle(thr); 581 return thrd_success; 582} 583 584// 7.25.5.7 585static inline void 586thrd_sleep(const struct timespec *time_point, struct timespec *remaining) 587{ 588 assert(time_point); 589 assert(!remaining); /* not implemented */ 590 Sleep(impl_timespec2msec(time_point)); 591} 592 593// 7.25.5.8 594static inline void 595thrd_yield(void) 596{ 597 SwitchToThread(); 598} 599 600 601/*----------- 7.25.6 Thread-specific storage functions -----------*/ 602// 7.25.6.1 603static inline int 604tss_create(tss_t *key, tss_dtor_t dtor) 605{ 606 if (!key) return thrd_error; 607 *key = TlsAlloc(); 608 if (dtor) { 609 if (impl_tss_dtor_register(*key, dtor)) { 610 TlsFree(*key); 611 return thrd_error; 612 } 613 } 614 return (*key != 0xFFFFFFFF) ? thrd_success : thrd_error; 615} 616 617// 7.25.6.2 618static inline void 619tss_delete(tss_t key) 620{ 621 TlsFree(key); 622} 623 624// 7.25.6.3 625static inline void * 626tss_get(tss_t key) 627{ 628 return TlsGetValue(key); 629} 630 631// 7.25.6.4 632static inline int 633tss_set(tss_t key, void *val) 634{ 635 return TlsSetValue(key, val) ? thrd_success : thrd_error; 636} 637 638 639/*-------------------- 7.25.7 Time functions --------------------*/ 640// 7.25.6.1 641#ifndef HAVE_TIMESPEC_GET 642static inline int 643timespec_get(struct timespec *ts, int base) 644{ 645 if (!ts) return 0; 646 if (base == TIME_UTC) { 647 ts->tv_sec = time(NULL); 648 ts->tv_nsec = 0; 649 return base; 650 } 651 return 0; 652} 653#endif 654