ceval_gil.h 8.92 KB
Newer Older
Antoine Pitrou's avatar
Antoine Pitrou committed
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
/*
 * Implementation of the Global Interpreter Lock (GIL).
 */

#include <stdlib.h>
#include <errno.h>


/* First some general settings */

/* microseconds (the Python API uses seconds, though) */
#define DEFAULT_INTERVAL 5000
static unsigned long gil_interval = DEFAULT_INTERVAL;
#define INTERVAL (gil_interval >= 1 ? gil_interval : 1)

/* Enable if you want to force the switching of threads at least every `gil_interval` */
#undef FORCE_SWITCHING
#define FORCE_SWITCHING


/*
   Notes about the implementation:

   - The GIL is just a boolean variable (gil_locked) whose access is protected
     by a mutex (gil_mutex), and whose changes are signalled by a condition
     variable (gil_cond). gil_mutex is taken for short periods of time,
     and therefore mostly uncontended.

   - In the GIL-holding thread, the main loop (PyEval_EvalFrameEx) must be
     able to release the GIL on demand by another thread. A volatile boolean
     variable (gil_drop_request) is used for that purpose, which is checked
     at every turn of the eval loop. That variable is set after a wait of
     `interval` microseconds on `gil_cond` has timed out.
34

Antoine Pitrou's avatar
Antoine Pitrou committed
35 36 37 38 39 40 41 42 43
      [Actually, another volatile boolean variable (eval_breaker) is used
       which ORs several conditions into one. Volatile booleans are
       sufficient as inter-thread signalling means since Python is run
       on cache-coherent architectures only.]

   - A thread wanting to take the GIL will first let pass a given amount of
     time (`interval` microseconds) before setting gil_drop_request. This
     encourages a defined switching period, but doesn't enforce it since
     opcodes can take an arbitrary time to execute.
44

Antoine Pitrou's avatar
Antoine Pitrou committed
45 46 47 48 49 50 51 52 53
     The `interval` value is available for the user to read and modify
     using the Python API `sys.{get,set}switchinterval()`.

   - When a thread releases the GIL and gil_drop_request is set, that thread
     ensures that another GIL-awaiting thread gets scheduled.
     It does so by waiting on a condition variable (switch_cond) until
     the value of gil_last_holder is changed to something else than its
     own thread state pointer, indicating that another thread was able to
     take the GIL.
54

Antoine Pitrou's avatar
Antoine Pitrou committed
55 56 57 58 59 60 61
     This is meant to prohibit the latency-adverse behaviour on multi-core
     machines where one thread would speculatively release the GIL, but still
     run and end up being the first to re-acquire it, making the "timeslices"
     much longer than expected.
     (Note: this mechanism is enabled with FORCE_SWITCHING above)
*/

62 63 64
#include "condvar.h"
#ifndef Py_HAVE_CONDVAR
#error You need either a POSIX-compatible or a Windows system!
Antoine Pitrou's avatar
Antoine Pitrou committed
65 66
#endif

67
#define MUTEX_T PyMUTEX_T
Antoine Pitrou's avatar
Antoine Pitrou committed
68
#define MUTEX_INIT(mut) \
69 70
    if (PyMUTEX_INIT(&(mut))) { \
        Py_FatalError("PyMUTEX_INIT(" #mut ") failed"); };
71
#define MUTEX_FINI(mut) \
72 73
    if (PyMUTEX_FINI(&(mut))) { \
        Py_FatalError("PyMUTEX_FINI(" #mut ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
74
#define MUTEX_LOCK(mut) \
75 76
    if (PyMUTEX_LOCK(&(mut))) { \
        Py_FatalError("PyMUTEX_LOCK(" #mut ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
77
#define MUTEX_UNLOCK(mut) \
78 79
    if (PyMUTEX_UNLOCK(&(mut))) { \
        Py_FatalError("PyMUTEX_UNLOCK(" #mut ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
80

81
#define COND_T PyCOND_T
Antoine Pitrou's avatar
Antoine Pitrou committed
82
#define COND_INIT(cond) \
83 84
    if (PyCOND_INIT(&(cond))) { \
        Py_FatalError("PyCOND_INIT(" #cond ") failed"); };
85
#define COND_FINI(cond) \
86 87
    if (PyCOND_FINI(&(cond))) { \
        Py_FatalError("PyCOND_FINI(" #cond ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
88
#define COND_SIGNAL(cond) \
89 90
    if (PyCOND_SIGNAL(&(cond))) { \
        Py_FatalError("PyCOND_SIGNAL(" #cond ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
91
#define COND_WAIT(cond, mut) \
92 93
    if (PyCOND_WAIT(&(cond), &(mut))) { \
        Py_FatalError("PyCOND_WAIT(" #cond ") failed"); };
Antoine Pitrou's avatar
Antoine Pitrou committed
94 95
#define COND_TIMED_WAIT(cond, mut, microseconds, timeout_result) \
    { \
96 97 98 99
        int r = PyCOND_TIMEDWAIT(&(cond), &(mut), (microseconds)); \
        if (r < 0) \
            Py_FatalError("PyCOND_WAIT(" #cond ") failed"); \
        if (r) /* 1 == timeout, 2 == impl. can't say, so assume timeout */ \
Antoine Pitrou's avatar
Antoine Pitrou committed
100 101 102 103 104 105 106
            timeout_result = 1; \
        else \
            timeout_result = 0; \
    } \



107
/* Whether the GIL is already taken (-1 if uninitialized). This is atomic
Antoine Pitrou's avatar
Antoine Pitrou committed
108
   because it can be read without any lock taken in ceval.c. */
109
static _Py_atomic_int gil_locked = {-1};
Antoine Pitrou's avatar
Antoine Pitrou committed
110 111
/* Number of GIL switches since the beginning. */
static unsigned long gil_switch_number = 0;
112 113
/* Last PyThreadState holding / having held the GIL. This helps us know
   whether anyone else was scheduled after we dropped the GIL. */
114
static _Py_atomic_address gil_last_holder = {0};
Antoine Pitrou's avatar
Antoine Pitrou committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

/* This condition variable allows one or several threads to wait until
   the GIL is released. In addition, the mutex also protects the above
   variables. */
static COND_T gil_cond;
static MUTEX_T gil_mutex;

#ifdef FORCE_SWITCHING
/* This condition variable helps the GIL-releasing thread wait for
   a GIL-awaiting thread to be scheduled and take the GIL. */
static COND_T switch_cond;
static MUTEX_T switch_mutex;
#endif


static int gil_created(void)
{
132
    return _Py_atomic_load_explicit(&gil_locked, _Py_memory_order_acquire) >= 0;
Antoine Pitrou's avatar
Antoine Pitrou committed
133 134 135 136 137 138 139 140 141 142 143 144
}

static void create_gil(void)
{
    MUTEX_INIT(gil_mutex);
#ifdef FORCE_SWITCHING
    MUTEX_INIT(switch_mutex);
#endif
    COND_INIT(gil_cond);
#ifdef FORCE_SWITCHING
    COND_INIT(switch_cond);
#endif
145
    _Py_atomic_store_relaxed(&gil_last_holder, 0);
146 147
    _Py_ANNOTATE_RWLOCK_CREATE(&gil_locked);
    _Py_atomic_store_explicit(&gil_locked, 0, _Py_memory_order_release);
Antoine Pitrou's avatar
Antoine Pitrou committed
148 149
}

150 151
static void destroy_gil(void)
{
152 153 154
    /* some pthread-like implementations tie the mutex to the cond
     * and must have the cond destroyed first.
     */
155
    COND_FINI(gil_cond);
156
    MUTEX_FINI(gil_mutex);
157 158
#ifdef FORCE_SWITCHING
    COND_FINI(switch_cond);
159
    MUTEX_FINI(switch_mutex);
160 161 162 163 164
#endif
    _Py_atomic_store_explicit(&gil_locked, -1, _Py_memory_order_release);
    _Py_ANNOTATE_RWLOCK_DESTROY(&gil_locked);
}

Antoine Pitrou's avatar
Antoine Pitrou committed
165 166
static void recreate_gil(void)
{
167
    _Py_ANNOTATE_RWLOCK_DESTROY(&gil_locked);
168
    /* XXX should we destroy the old OS resources here? */
Antoine Pitrou's avatar
Antoine Pitrou committed
169 170 171 172 173
    create_gil();
}

static void drop_gil(PyThreadState *tstate)
{
174
    if (!_Py_atomic_load_relaxed(&gil_locked))
Antoine Pitrou's avatar
Antoine Pitrou committed
175
        Py_FatalError("drop_gil: GIL is not locked");
176 177 178 179 180
    /* tstate is allowed to be NULL (early interpreter init) */
    if (tstate != NULL) {
        /* Sub-interpreter support: threads might have been switched
           under our feet using PyThreadState_Swap(). Fix the GIL last
           holder variable so that our heuristics work. */
181
        _Py_atomic_store_relaxed(&gil_last_holder, (uintptr_t)tstate);
182
    }
Antoine Pitrou's avatar
Antoine Pitrou committed
183 184

    MUTEX_LOCK(gil_mutex);
185 186
    _Py_ANNOTATE_RWLOCK_RELEASED(&gil_locked, /*is_write=*/1);
    _Py_atomic_store_relaxed(&gil_locked, 0);
Antoine Pitrou's avatar
Antoine Pitrou committed
187 188
    COND_SIGNAL(gil_cond);
    MUTEX_UNLOCK(gil_mutex);
189

Antoine Pitrou's avatar
Antoine Pitrou committed
190
#ifdef FORCE_SWITCHING
191
    if (_Py_atomic_load_relaxed(&gil_drop_request) && tstate != NULL) {
Antoine Pitrou's avatar
Antoine Pitrou committed
192 193
        MUTEX_LOCK(switch_mutex);
        /* Not switched yet => wait */
194
        if ((PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder) == tstate) {
195
        RESET_GIL_DROP_REQUEST();
196 197 198
            /* NOTE: if COND_WAIT does not atomically start waiting when
               releasing the mutex, another thread can run through, take
               the GIL and drop it again, and reset the condition
199
               before we even had a chance to wait for it. */
Antoine Pitrou's avatar
Antoine Pitrou committed
200
            COND_WAIT(switch_cond, switch_mutex);
201
    }
Antoine Pitrou's avatar
Antoine Pitrou committed
202 203 204 205 206 207 208 209 210 211 212 213 214 215
        MUTEX_UNLOCK(switch_mutex);
    }
#endif
}

static void take_gil(PyThreadState *tstate)
{
    int err;
    if (tstate == NULL)
        Py_FatalError("take_gil: NULL tstate");

    err = errno;
    MUTEX_LOCK(gil_mutex);

216
    if (!_Py_atomic_load_relaxed(&gil_locked))
Antoine Pitrou's avatar
Antoine Pitrou committed
217
        goto _ready;
218

219
    while (_Py_atomic_load_relaxed(&gil_locked)) {
Antoine Pitrou's avatar
Antoine Pitrou committed
220 221 222 223 224 225 226
        int timed_out = 0;
        unsigned long saved_switchnum;

        saved_switchnum = gil_switch_number;
        COND_TIMED_WAIT(gil_cond, gil_mutex, INTERVAL, timed_out);
        /* If we timed out and no switch occurred in the meantime, it is time
           to ask the GIL-holding thread to drop it. */
227 228 229
        if (timed_out &&
            _Py_atomic_load_relaxed(&gil_locked) &&
            gil_switch_number == saved_switchnum) {
Antoine Pitrou's avatar
Antoine Pitrou committed
230 231 232 233 234 235 236 237 238
            SET_GIL_DROP_REQUEST();
        }
    }
_ready:
#ifdef FORCE_SWITCHING
    /* This mutex must be taken before modifying gil_last_holder (see drop_gil()). */
    MUTEX_LOCK(switch_mutex);
#endif
    /* We now hold the GIL */
239 240
    _Py_atomic_store_relaxed(&gil_locked, 1);
    _Py_ANNOTATE_RWLOCK_ACQUIRED(&gil_locked, /*is_write=*/1);
Antoine Pitrou's avatar
Antoine Pitrou committed
241

242
    if (tstate != (PyThreadState*)_Py_atomic_load_relaxed(&gil_last_holder)) {
243
        _Py_atomic_store_relaxed(&gil_last_holder, (uintptr_t)tstate);
Antoine Pitrou's avatar
Antoine Pitrou committed
244 245
        ++gil_switch_number;
    }
246

Antoine Pitrou's avatar
Antoine Pitrou committed
247 248 249 250
#ifdef FORCE_SWITCHING
    COND_SIGNAL(switch_cond);
    MUTEX_UNLOCK(switch_mutex);
#endif
251
    if (_Py_atomic_load_relaxed(&gil_drop_request)) {
Antoine Pitrou's avatar
Antoine Pitrou committed
252 253 254 255 256
        RESET_GIL_DROP_REQUEST();
    }
    if (tstate->async_exc != NULL) {
        _PyEval_SignalAsyncExc();
    }
257

Antoine Pitrou's avatar
Antoine Pitrou committed
258 259 260 261 262 263 264 265 266 267 268 269 270
    MUTEX_UNLOCK(gil_mutex);
    errno = err;
}

void _PyEval_SetSwitchInterval(unsigned long microseconds)
{
    gil_interval = microseconds;
}

unsigned long _PyEval_GetSwitchInterval()
{
    return gil_interval;
}