Summary
Lock and Semaphore use lazy initialization for their _waiters deque (initialized to None, allocated on first contention), while Event and Condition eagerly allocate collections.deque() in __init__.
The lazy pattern was introduced for Lock and later applied to Semaphore in GH-97545 ("Make Semaphore run faster", merged by @gvanrossum). The same optimization was not applied to Event and Condition.
Impact
- Consistency: Four synchronization primitives in the same module use two different initialization patterns for the same data structure.
- Performance: Creating an
Event or Condition that never gets waited on incurs an unnecessary collections.deque() allocation (~42ns per creation, ~13x slower than None assignment on the init path).
- Maintenance: The inconsistency means contributors working on one primitive may not realize the other primitives follow a different pattern.
Proposed fix
Apply the same lazy initialization to Event and Condition:
- Initialize
_waiters = None in __init__
- Allocate
collections.deque() on first wait() call
- Add null guards in
set(), _notify(), and notify_all()
All existing tests pass with the change.
Linked PRs
Summary
LockandSemaphoreuse lazy initialization for their_waitersdeque (initialized toNone, allocated on first contention), whileEventandConditioneagerly allocatecollections.deque()in__init__.The lazy pattern was introduced for
Lockand later applied toSemaphorein GH-97545 ("Make Semaphore run faster", merged by @gvanrossum). The same optimization was not applied toEventandCondition.Impact
EventorConditionthat never gets waited on incurs an unnecessarycollections.deque()allocation (~42ns per creation, ~13x slower thanNoneassignment on the init path).Proposed fix
Apply the same lazy initialization to
EventandCondition:_waiters = Nonein__init__collections.deque()on firstwait()callset(),_notify(), andnotify_all()All existing tests pass with the change.
Linked PRs