Skip to content

Commit 35f6301

Browse files
authored
bpo-10978: Semaphores can release multiple threads at a time (GH-15588)
1 parent 0dac68f commit 35f6301

File tree

4 files changed

+56
-13
lines changed

4 files changed

+56
-13
lines changed

Doc/library/threading.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -802,11 +802,14 @@ Semaphores also support the :ref:`context management protocol <with-locks>`.
802802
.. versionchanged:: 3.2
803803
The *timeout* parameter is new.
804804

805-
.. method:: release()
805+
.. method:: release(n=1)
806+
807+
Release a semaphore, incrementing the internal counter by *n*. When it
808+
was zero on entry and other threads are waiting for it to become larger
809+
than zero again, wake up *n* of those threads.
806810

807-
Release a semaphore, incrementing the internal counter by one. When it
808-
was zero on entry and another thread is waiting for it to become larger
809-
than zero again, wake up that thread.
811+
.. versionchanged:: 3.9
812+
Added the *n* parameter to release multiple waiting threads at once.
810813

811814

812815
.. class:: BoundedSemaphore(value=1)

Lib/test/lock_tests.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,38 @@ def f():
663663
b.wait_for_finished()
664664
self.assertEqual(sem_results, [True] * (6 + 7 + 6 + 1))
665665

666+
def test_multirelease(self):
667+
sem = self.semtype(7)
668+
sem.acquire()
669+
results1 = []
670+
results2 = []
671+
phase_num = 0
672+
def f():
673+
sem.acquire()
674+
results1.append(phase_num)
675+
sem.acquire()
676+
results2.append(phase_num)
677+
b = Bunch(f, 10)
678+
b.wait_for_started()
679+
while len(results1) + len(results2) < 6:
680+
_wait()
681+
self.assertEqual(results1 + results2, [0] * 6)
682+
phase_num = 1
683+
sem.release(7)
684+
while len(results1) + len(results2) < 13:
685+
_wait()
686+
self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7)
687+
phase_num = 2
688+
sem.release(6)
689+
while len(results1) + len(results2) < 19:
690+
_wait()
691+
self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7 + [2] * 6)
692+
# The semaphore is still locked
693+
self.assertFalse(sem.acquire(False))
694+
# Final release, to let the last thread finish
695+
sem.release()
696+
b.wait_for_finished()
697+
666698
def test_try_acquire(self):
667699
sem = self.semtype(2)
668700
self.assertTrue(sem.acquire(False))

Lib/threading.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -439,16 +439,19 @@ def acquire(self, blocking=True, timeout=None):
439439

440440
__enter__ = acquire
441441

442-
def release(self):
443-
"""Release a semaphore, incrementing the internal counter by one.
442+
def release(self, n=1):
443+
"""Release a semaphore, incrementing the internal counter by one or more.
444444
445445
When the counter is zero on entry and another thread is waiting for it
446446
to become larger than zero again, wake up that thread.
447447
448448
"""
449+
if n < 1:
450+
raise ValueError('n must be one or more')
449451
with self._cond:
450-
self._value += 1
451-
self._cond.notify()
452+
self._value += n
453+
for i in range(n):
454+
self._cond.notify()
452455

453456
def __exit__(self, t, v, tb):
454457
self.release()
@@ -475,8 +478,8 @@ def __init__(self, value=1):
475478
Semaphore.__init__(self, value)
476479
self._initial_value = value
477480

478-
def release(self):
479-
"""Release a semaphore, incrementing the internal counter by one.
481+
def release(self, n=1):
482+
"""Release a semaphore, incrementing the internal counter by one or more.
480483
481484
When the counter is zero on entry and another thread is waiting for it
482485
to become larger than zero again, wake up that thread.
@@ -485,11 +488,14 @@ def release(self):
485488
raise a ValueError.
486489
487490
"""
491+
if n < 1:
492+
raise ValueError('n must be one or more')
488493
with self._cond:
489-
if self._value >= self._initial_value:
494+
if self._value + n > self._initial_value:
490495
raise ValueError("Semaphore released too many times")
491-
self._value += 1
492-
self._cond.notify()
496+
self._value += n
497+
for i in range(n):
498+
self._cond.notify()
493499

494500

495501
class Event:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Semaphores and BoundedSemaphores can now release more than one waiting
2+
thread at a time.

0 commit comments

Comments
 (0)