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