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-2024 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
|