diff options
Diffstat (limited to 'gl/pthread-once.c')
| -rw-r--r-- | gl/pthread-once.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/gl/pthread-once.c b/gl/pthread-once.c new file mode 100644 index 00000000..b19dae50 --- /dev/null +++ b/gl/pthread-once.c | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | /* POSIX once-only control. | ||
| 2 | Copyright (C) 2019-2025 Free Software Foundation, Inc. | ||
| 3 | |||
| 4 | This file is free software: you can redistribute it and/or modify | ||
| 5 | it under the terms of the GNU Lesser General Public License as | ||
| 6 | published by the Free Software Foundation; either version 2.1 of the | ||
| 7 | License, or (at your option) any later version. | ||
| 8 | |||
| 9 | This file is distributed in the hope that it will be useful, | ||
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | GNU Lesser General Public License for more details. | ||
| 13 | |||
| 14 | You should have received a copy of the GNU Lesser General Public License | ||
| 15 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ | ||
| 16 | |||
| 17 | /* Written by Bruno Haible <bruno@clisp.org>, 2019. */ | ||
| 18 | |||
| 19 | #include <config.h> | ||
| 20 | |||
| 21 | /* Specification. */ | ||
| 22 | #include <pthread.h> | ||
| 23 | |||
| 24 | #if (defined _WIN32 && ! defined __CYGWIN__) && USE_WINDOWS_THREADS | ||
| 25 | # include "windows-once.h" | ||
| 26 | #endif | ||
| 27 | |||
| 28 | #if (defined _WIN32 && ! defined __CYGWIN__) && USE_WINDOWS_THREADS | ||
| 29 | /* Use Windows threads. */ | ||
| 30 | |||
| 31 | int | ||
| 32 | pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) | ||
| 33 | { | ||
| 34 | glwthread_once (once_control, initfunction); | ||
| 35 | return 0; | ||
| 36 | } | ||
| 37 | |||
| 38 | #elif HAVE_PTHREAD_H | ||
| 39 | /* Provide workarounds for POSIX threads. */ | ||
| 40 | |||
| 41 | # if defined __CYGWIN__ | ||
| 42 | |||
| 43 | # include <stdlib.h> | ||
| 44 | |||
| 45 | int | ||
| 46 | pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) | ||
| 47 | { | ||
| 48 | /* In this implementation, we reuse the type | ||
| 49 | typedef struct { pthread_mutex_t mutex; int state; } pthread_once_t; | ||
| 50 | #define PTHREAD_ONCE_INIT { PTHREAD_MUTEX_INITIALIZER, 0 } | ||
| 51 | while assigning the following meaning to the state: | ||
| 52 | state = (<number of waiting threads> << 16) + <1 if done> | ||
| 53 | In other words: | ||
| 54 | state = { unsigned int num_threads : 16; unsigned int done : 16; } | ||
| 55 | */ | ||
| 56 | struct actual_state | ||
| 57 | { | ||
| 58 | _Atomic unsigned short num_threads; | ||
| 59 | /* done == 0: initial state | ||
| 60 | done == 1: initfunction executed, lock still active | ||
| 61 | done == 2: initfunction executed, lock no longer usable */ | ||
| 62 | _Atomic unsigned short done; | ||
| 63 | }; | ||
| 64 | struct actual_state *state_p = (struct actual_state *) &once_control->state; | ||
| 65 | /* This test is not necessary. It's only an optimization, to establish | ||
| 66 | a fast path for the common case that the 'done' word is already > 0. */ | ||
| 67 | if (state_p->done == 0) | ||
| 68 | { | ||
| 69 | /* Increment num_threads (atomically), to indicate that this thread will | ||
| 70 | possibly take the lock. */ | ||
| 71 | state_p->num_threads += 1; | ||
| 72 | /* Test the 'done' word. */ | ||
| 73 | if (state_p->done == 0) | ||
| 74 | { | ||
| 75 | /* The 'done' word is still zero. Now take the lock. */ | ||
| 76 | pthread_mutex_lock (&once_control->mutex); | ||
| 77 | /* Test the 'done' word again. */ | ||
| 78 | if (state_p->done == 0) | ||
| 79 | { | ||
| 80 | /* Execute the initfunction. */ | ||
| 81 | (*initfunction) (); | ||
| 82 | /* Set the 'done' word to 1 (atomically). */ | ||
| 83 | state_p->done = 1; | ||
| 84 | } | ||
| 85 | /* Now the 'done' word is 1. Release the lock. */ | ||
| 86 | pthread_mutex_unlock (&once_control->mutex); | ||
| 87 | } | ||
| 88 | /* Here, done is > 0. */ | ||
| 89 | /* Decrement num_threads (atomically). */ | ||
| 90 | if ((state_p->num_threads -= 1) == 0) | ||
| 91 | { | ||
| 92 | /* num_threads is now zero, and done is > 0. | ||
| 93 | No other thread will need to use the lock. | ||
| 94 | We can therefore destroy the lock, to free resources. */ | ||
| 95 | if (__sync_bool_compare_and_swap (&state_p->done, 1, 2)) | ||
| 96 | pthread_mutex_destroy (&once_control->mutex); | ||
| 97 | } | ||
| 98 | } | ||
| 99 | /* Proof of correctness: | ||
| 100 | * num_threads is incremented and then decremented by some threads. | ||
| 101 | Therefore, num_threads always stays >= 0, and is == 0 at the end. | ||
| 102 | * The 'done' word, once > 0, stays > 0 (since it is never assigned 0). | ||
| 103 | * The 'done' word is changed from == 0 to > 0 only while the lock | ||
| 104 | is taken. Therefore, only the first thread that succeeds in taking | ||
| 105 | the lock executes the initfunction and sets the 'done' word to a | ||
| 106 | value > 0; the other threads that take the lock do no side effects | ||
| 107 | between taking and releasing the lock. | ||
| 108 | * The 'done' word does not change any more once it is 2. | ||
| 109 | Therefore, it can be changed from 1 to 2 only once. | ||
| 110 | * pthread_mutex_destroy gets invoked right after 'done' has been changed | ||
| 111 | from 1 to 2. Therefore, pthread_mutex_destroy gets invoked only once. | ||
| 112 | * After a moment where num_threads was 0 and done was > 0, no thread can | ||
| 113 | reach the pthread_mutex_lock invocation. Proof: | ||
| 114 | - At such a moment, no thread is in the code range between | ||
| 115 | state_p->num_threads += 1 | ||
| 116 | and | ||
| 117 | state_p->num_threads -= 1 | ||
| 118 | - After such a moment, some thread can increment num_threads, but from | ||
| 119 | there they cannot reach the pthread_mutex_lock invocation, because the | ||
| 120 | if (state_p->done == 0) | ||
| 121 | test prevents that. | ||
| 122 | * From this it follows that: | ||
| 123 | - pthread_mutex_destroy cannot be executed while the lock is taken | ||
| 124 | (because pthread_mutex_destroy is only executed after a moment where | ||
| 125 | num_threads was 0 and done was > 0). | ||
| 126 | - Once pthread_mutex_destroy has been executed, the lock is not used any | ||
| 127 | more. | ||
| 128 | */ | ||
| 129 | return 0; | ||
| 130 | } | ||
| 131 | |||
| 132 | # endif | ||
| 133 | |||
| 134 | #else | ||
| 135 | /* Provide a dummy implementation for single-threaded applications. */ | ||
| 136 | |||
| 137 | int | ||
| 138 | pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) | ||
| 139 | { | ||
| 140 | if (*once_control == 0) | ||
| 141 | { | ||
| 142 | *once_control = ~ 0; | ||
| 143 | initfunction (); | ||
| 144 | } | ||
| 145 | return 0; | ||
| 146 | } | ||
| 147 | |||
| 148 | #endif | ||
