Documentation
¶
Overview ¶
Package pysync ports the synchronization primitives in cpython/Python/lock.c, parking_lot.c, and critical_section.c.
The package is named pysync (not sync) because the primitives have semantics distinct from Go's standard sync package: byte-flag PyMutex with parked-waiter bits, address-keyed parking, PEP 703 critical sections, and so on.
Index ¶
- func AfterFork()
- func Unpark(addr unsafe.Pointer, fn UnparkFn)
- func UnparkAll(addr unsafe.Pointer)
- type CSThread
- type CriticalSection
- type Event
- type LockFlags
- type LockStatus
- type Mutex
- type OnceFlag
- type OnceFn
- type ParkStatus
- type RWMutex
- type RawMutex
- type RecursiveMutex
- type SeqLock
- type UnparkFn
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AfterFork ¶
func AfterFork()
AfterFork resets the parking lot. In CPython this is required after fork() because parked waiters belong to threads that no longer exist in the child. Go programs do not POSIX-fork the runtime, so the function is preserved for source-shape parity but is a no-op in practice. Calling it on a live process would deadlock waiters; only call it when the program is single-threaded.
CPython: Python/parking_lot.c:L434 _PyParkingLot_AfterFork
Types ¶
type CSThread ¶
type CSThread struct {
// contains filtered or unexported fields
}
CSThread holds the critical-section stack head for a single thread of execution. Pass the same value to every Begin and End call from the goroutine that owns it.
CPython: Python/critical_section.c:L95 _PyCriticalSection_SuspendAll (adapted from)
func (*CSThread) Top ¶
func (t *CSThread) Top() *CriticalSection
Top returns the innermost critical section, or nil if none is active.
CPython: Python/critical_section.c:L95 _PyCriticalSection_SuspendAll (adapted from)
type CriticalSection ¶
type CriticalSection struct {
// contains filtered or unexported fields
}
CriticalSection mirrors PyCriticalSection (PEP 703).
The full design serializes access to a Python object across threads when the GIL is disabled. In a GIL build (the default and the only build gopy supports in v0.1) the GIL already serializes, so Begin and End reduce to bookkeeping: push and pop a per-thread linked stack. v0.14 will turn the bookkeeping into real mutex acquisitions when the pygil_disabled tag is enabled.
The per-thread stack head lives in CSThread for now because gopy has no goroutine-local thread state. v0.7 (state package) will fold this into the real thread state.
CPython: Include/cpython/critical_section.h:L108 PyCriticalSection
func (*CriticalSection) Begin ¶
func (c *CriticalSection) Begin(t *CSThread, m *Mutex)
Begin starts a critical section guarded by m. In v0.1 (GIL build) this only pushes onto the per-thread stack. v0.14 will acquire m.
CPython: Python/critical_section.c:L21 _PyCriticalSection_BeginSlow
func (*CriticalSection) BeginMutex2 ¶
func (c *CriticalSection) BeginMutex2(t *CSThread, m1, m2 *Mutex)
BeginMutex2 starts a critical section guarding two mutexes. The acquisition order is fixed by m1 then m2 to prevent deadlocks between callers that touch the same pair, mirroring _PyCriticalSection2 in C.
CPython: Python/critical_section.c:L66 _PyCriticalSection2_BeginSlow
func (*CriticalSection) End ¶
func (c *CriticalSection) End(t *CSThread)
End pops the section. It panics if c is not on top of t's stack, which would indicate a Begin/End mismatch.
CPython: Python/critical_section.c:L173 PyCriticalSection_End
type Event ¶
type Event struct {
// contains filtered or unexported fields
}
Event mirrors PyEvent from cpython/Python/lock.c. It is a one-shot flag: once Notified, it stays set forever and every Wait returns immediately.
State machine on the byte-flag word:
0 unset, no waiters flagLocked set flagHasParked unset, with parked waiters
CPython: Include/internal/pycore_lock.h:L67 PyEvent
func (*Event) IsSet ¶
IsSet reports whether the event has been notified.
CPython: Python/lock.c:L256 _PyEvent_IsSet
func (*Event) Notify ¶
func (e *Event) Notify()
Notify sets the event. If waiters are parked, all are woken. Repeat calls are safe and have no effect.
CPython: Python/lock.c:L263 _PyEvent_Notify
func (*Event) Wait ¶
func (e *Event) Wait()
Wait blocks until the event is set.
CPython: Python/lock.c:L281 PyEvent_Wait
type LockFlags ¶
type LockFlags uint32
LockFlags bitset for LockTimed.
CPython: Include/internal/pycore_lock.h:L35 _PyLockFlags
const ( // LockDetach asks the runtime to detach the current thread state // while parked. v0.1 has no thread state, so this is a no-op. LockDetach LockFlags = 1 << 0 // LockHandleSignals asks the runtime to run pending signal // handlers if the wait was interrupted. v0.1 has no signal // plumbing, so this is a no-op. LockHandleSignals LockFlags = 1 << 1 )
type LockStatus ¶
type LockStatus int
LockStatus mirrors PyLockStatus.
CPython: Include/pythread.h:L12 PyLockStatus
const ( // LockFailure means the lock could not be acquired. LockFailure LockStatus = iota // LockAcquired means the lock is now held by the caller. LockAcquired // LockIntr means the wait was interrupted by a signal. v0.1 has // no signal plumbing, so this value is reserved. LockIntr )
type Mutex ¶
type Mutex struct {
// contains filtered or unexported fields
}
Mutex is the byte-flag mutex from cpython/Python/lock.c.
The zero value is an unlocked mutex. Mutex must not be copied after first use, the same as Go's sync.Mutex.
CPython: Include/cpython/lock.h:L29 PyMutex
func (*Mutex) IsLocked ¶
IsLocked reports whether the mutex is currently held.
CPython: Python/lock.c:L635 PyMutex_IsLocked
func (*Mutex) Lock ¶
func (m *Mutex) Lock()
Lock blocks until the mutex is acquired.
CPython: Python/lock.c:L618 PyMutex_Lock
func (*Mutex) LockTimed ¶
func (m *Mutex) LockTimed(timeout time.Duration, flags LockFlags) LockStatus
LockTimed acquires the mutex with a timeout. timeout < 0 means block forever; timeout == 0 is a non-blocking try.
CPython: Python/lock.c:L53 _PyMutex_LockTimed
type OnceFlag ¶
type OnceFlag struct {
// contains filtered or unexported fields
}
OnceFlag is a one-shot initialization flag. The zero value is ready to use.
CPython: Include/cpython/modsupport.h:L7 _PyOnceFlag
type OnceFn ¶
type OnceFn func() error
OnceFn is the function invoked by OnceFlag.Do. Returning a non-nil error resets the flag so a later caller can retry.
CPython: Include/internal/pycore_lock.h:L127 _Py_once_fn_t
type ParkStatus ¶
type ParkStatus int
ParkStatus is the result of a Park call.
CPython: Include/internal/pycore_parking_lot.h:L22 Py_PARK_OK enum (adapted from)
const ( // ParkOK means the thread was woken by Unpark or UnparkAll. ParkOK ParkStatus = iota // ParkTimeout means the timeout elapsed before a wakeup. ParkTimeout // ParkAgain means the address-equality check failed before parking. ParkAgain // ParkIntr is reserved for future signal handling. It is never // returned in v0.1 because gopy has no signal plumbing yet. ParkIntr )
func Park ¶
func Park(addr unsafe.Pointer, check func() bool, timeout time.Duration, parkArg any, detach bool, ) ParkStatus
Park atomically checks `check()` and, if it returns true, blocks the caller until Unpark or UnparkAll fires for the same addr, the timeout elapses, or the address-equality check fails before parking.
addr is the unique key. It is hashed by uintptr identity, not by the bytes it points at; the C code's `atomic_memcmp` step is the caller's responsibility, performed inside `check`.
If check returns false, Park returns ParkAgain without blocking.
timeout < 0 means "no timeout". timeout == 0 still parks (matches CPython: zero is special only inside Mutex.LockTimed, not at the parking-lot layer). Callers wanting a no-block try should not call Park.
detach is plumbed through for the PEP 703 attach/detach protocol; it has no effect in v0.1.
CPython: Python/parking_lot.c:L329 _PyParkingLot_Park
type RWMutex ¶
type RWMutex struct {
// contains filtered or unexported fields
}
RWMutex mirrors _PyRWMutex from cpython/Python/lock.c. It is a reader-writer lock with the same bit packing as the C code:
bit 0 writer holds the lock bit 1 at least one waiter is parked bits 2.. reader count
Writers are not starved: once a writer parks, new readers also park rather than slipping past.
The zero value is an unlocked RWMutex. Like the other pysync primitives, it must not be copied after first use.
CPython: Include/internal/pycore_lock.h:L187 _PyRWMutex
func (*RWMutex) Lock ¶
func (r *RWMutex) Lock()
Lock acquires the write lock, parking until both readers and any other writer have released.
CPython: Python/lock.c:L508 _PyRWMutex_Lock
func (*RWMutex) RLock ¶
func (r *RWMutex) RLock()
RLock acquires a read lock, parking until both the writer slot is free and no writer is waiting.
CPython: Python/lock.c:L464 _PyRWMutex_RLock
type RawMutex ¶
type RawMutex struct {
// contains filtered or unexported fields
}
RawMutex mirrors _PyRawMutex from cpython/Python/lock.c. The C implementation packs the linked list of waiters into the mutex word itself, so it does not need the parking lot. CPython uses it only as the per-bucket lock inside the parking lot, where reusing the parking lot would create a bootstrap cycle.
In gopy the parking-lot bucket lock is Go's sync.Mutex, so the bootstrap cycle does not exist. This type is a thin wrapper around sync.Mutex provided for source-shape parity. The pointer-tagged uintptr trick that lock.c uses cannot be expressed without triggering go vet's unsafeptr check; if a future caller needs the exact CPython memory layout we will revisit.
CPython: Include/internal/pycore_lock.h:L97 _PyRawMutex
type RecursiveMutex ¶
type RecursiveMutex struct {
// contains filtered or unexported fields
}
RecursiveMutex mirrors _PyRecursiveMutex from cpython/Python/lock.c. The same goroutine ident may relock; other goroutines block until every relock has been released.
The owner is identified by a pythread.Ident, so RecursiveMutex is only useful when callers have an explicit ident to pass. v0.1's usage is bounded; v0.7 (state) wires it up to the per-thread state.
CPython: Include/internal/pycore_lock.h:L149 _PyRecursiveMutex
func (*RecursiveMutex) IsLockedBy ¶
func (r *RecursiveMutex) IsLockedBy(id pythread.Ident) bool
IsLockedBy reports whether the recursive mutex is currently held by the given ident.
CPython: Python/lock.c:L375 _PyRecursiveMutex_IsLockedByCurrentThread
func (*RecursiveMutex) Lock ¶
func (r *RecursiveMutex) Lock(id pythread.Ident)
Lock acquires the mutex on behalf of id. If id already holds it, the recursion depth is incremented.
CPython: Python/lock.c:L381 _PyRecursiveMutex_Lock
func (*RecursiveMutex) Unlock ¶
func (r *RecursiveMutex) Unlock(id pythread.Ident)
Unlock releases one level. It panics if id does not hold the mutex.
CPython: Python/lock.c:L410 _PyRecursiveMutex_Unlock
type SeqLock ¶
type SeqLock struct {
// contains filtered or unexported fields
}
SeqLock mirrors _PySeqLock from cpython/Python/lock.c. Writers take an exclusive lock that toggles the low bit on entry and exit; readers retry if the sequence number changed mid-read or is odd.
CPython: Include/internal/pycore_lock.h:L208 _PySeqLock
func (*SeqLock) AbandonWrite ¶
func (s *SeqLock) AbandonWrite()
AbandonWrite rolls back a LockWrite without publishing changes.
CPython: Python/lock.c:L564 _PySeqLock_AbandonWrite
func (*SeqLock) AfterFork ¶
AfterFork resets the sequence if it was mid-update at fork time. Returns true if a reset happened.
CPython: Python/lock.c:L604 _PySeqLock_AfterFork
func (*SeqLock) BeginRead ¶
BeginRead returns a sequence token to pass to EndRead. It spins until a non-updating sequence is observed.
CPython: Python/lock.c:L578 _PySeqLock_BeginRead
func (*SeqLock) EndRead ¶
EndRead returns true if the sequence number is unchanged since the matching BeginRead. Callers retry the read on a false result.
CPython: Python/lock.c:L589 _PySeqLock_EndRead
func (*SeqLock) LockWrite ¶
func (s *SeqLock) LockWrite()
LockWrite acquires write access by moving the sequence to an odd value. Concurrent writers spin until the lock is released.
CPython: Python/lock.c:L543 _PySeqLock_LockWrite
func (*SeqLock) UnlockWrite ¶
func (s *SeqLock) UnlockWrite()
UnlockWrite publishes changes and releases the lock by moving the sequence to an even value.
CPython: Python/lock.c:L571 _PySeqLock_UnlockWrite
type UnparkFn ¶
UnparkFn is invoked under the bucket lock when Unpark finds a waiter (or with parkArg == nil if no waiter was queued). hasMore reports whether the bucket still has waiters on the same addr after the dequeue.
CPython: Include/internal/pycore_parking_lot.h _Py_unpark_fn_t (adapted from)