Skip to main content

Python/thread.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Python/thread.c

This annotation covers the PyThread_type_lock API — the portable locking primitive used throughout CPython for the GIL, the import lock, and per-object locks. See python_thread_detail for thread creation and python_thread2_detail for thread-local storage.

Map

LinesSymbolRole
1-80PyThread_allocate_lockCreate a new lock (backed by pthread_mutex or Win32 event)
81-180PyThread_acquire_lock_timedTry to acquire with optional timeout
181-280PyThread_acquire_lockBlocking acquire (no timeout)
281-360PyThread_release_lockRelease; error if not held by this thread
361-440PyThread_free_lockDestroy and free the lock
441-600_PyThread_cond_*Condition variable helpers used by the GIL

Reading

PyThread_allocate_lock

// CPython: Python/thread_pthread.h:380 PyThread_allocate_lock
PyThread_type_lock
PyThread_allocate_lock(void)
{
/* sem_t on platforms that have it; pthread_mutex_t otherwise */
#ifdef USE_SEMAPHORES
sem_t *lock = (sem_t *)PyMem_RawMalloc(sizeof(sem_t));
if (lock) {
if (sem_init(lock, 0, 1) < 0) {
PyMem_RawFree(lock);
lock = NULL;
}
}
#else
pthread_lock *lock = (pthread_lock *)PyMem_RawMalloc(sizeof(pthread_lock));
if (lock) {
lock->locked = 0;
pthread_mutex_init(&lock->mut, NULL);
pthread_cond_init(&lock->lock_released, NULL);
}
#endif
return (PyThread_type_lock)lock;
}

PyThread_acquire_lock_timed

// CPython: Python/thread_pthread.h:440 PyThread_acquire_lock_timed
PyLockStatus
PyThread_acquire_lock_timed(PyThread_type_lock lock,
PY_TIMEOUT_T microseconds, int intr_flag)
{
/* microseconds == 0 → try without blocking
microseconds == -1 → block indefinitely */
#ifdef USE_SEMAPHORES
sem_t *thelock = (sem_t *)lock;
int success;
if (microseconds == 0) {
success = sem_trywait(thelock) == 0;
} else if (microseconds < 0) {
do { success = sem_wait(thelock) == 0; }
while (!success && errno == EINTR && !intr_flag);
} else {
struct timespec ts = _PyDeadline_AsTimespec(microseconds);
do { success = sem_timedwait(thelock, &ts) == 0; }
while (!success && errno == EINTR && !intr_flag);
}
return success ? PY_LOCK_ACQUIRED : PY_LOCK_FAILURE;
#else
/* pthread_mutex + condition variable path */
...
#endif
}

intr_flag=1 allows the acquire to fail on EINTR (used when holding the GIL so signals can be handled).

PyThread_release_lock

// CPython: Python/thread_pthread.h:520 PyThread_release_lock
void
PyThread_release_lock(PyThread_type_lock lock)
{
#ifdef USE_SEMAPHORES
sem_post((sem_t *)lock);
#else
pthread_lock *thelock = (pthread_lock *)lock;
pthread_mutex_lock(&thelock->mut);
thelock->locked = 0;
pthread_mutex_unlock(&thelock->mut);
pthread_cond_signal(&thelock->lock_released);
#endif
}

Condition variables for the GIL

// CPython: Python/thread_pthread.h:560 _PyThread_cond_init
/* The GIL uses a condition variable to avoid busy-waiting.
A thread waiting for the GIL calls pthread_cond_timedwait;
the GIL holder calls pthread_cond_signal when releasing it. */
typedef struct {
pthread_cond_t cond;
pthread_mutex_t mutex;
} _PyThread_cond_t;

The GIL is not implemented as a PyThread_type_lock; it uses these lower-level condition variables directly for better latency.

gopy notes

gopy does not have a GIL. PyThread_type_lock maps to vm.PyLock (a sync.Mutex wrapper) in vm/thread_lock.go. PyThread_acquire_lock_timed maps to vm.PyLock.AcquireTimed(timeout time.Duration). The condition variable helpers have no equivalent since goroutine scheduling is handled by the Go runtime.