]> granicus.if.org Git - python/commitdiff
bpo-10978: Semaphores can release multiple threads at a time (GH-15588)
authorRaymond Hettinger <rhettinger@users.noreply.github.com>
Thu, 29 Aug 2019 08:45:19 +0000 (01:45 -0700)
committerGitHub <noreply@github.com>
Thu, 29 Aug 2019 08:45:19 +0000 (01:45 -0700)
Doc/library/threading.rst
Lib/test/lock_tests.py
Lib/threading.py
Misc/NEWS.d/next/Library/2019-08-29-01-19-13.bpo-10978.J6FQYY.rst [new file with mode: 0644]

index 7de12fbf9dcd396f3dddd13ba2f51993f375379c..0489421cdf60e5b0680c540058d49cc90e99b161 100644 (file)
@@ -802,11 +802,14 @@ Semaphores also support the :ref:`context management protocol <with-locks>`.
       .. versionchanged:: 3.2
          The *timeout* parameter is new.
 
-   .. method:: release()
+   .. method:: release(n=1)
+
+      Release a semaphore, incrementing the internal counter by *n*.  When it
+      was zero on entry and other threads are waiting for it to become larger
+      than zero again, wake up *n* of those threads.
 
-      Release a semaphore, incrementing the internal counter by one.  When it
-      was zero on entry and another thread is waiting for it to become larger
-      than zero again, wake up that thread.
+      .. versionchanged:: 3.9
+         Added the *n* parameter to release multiple waiting threads at once.
 
 
 .. class:: BoundedSemaphore(value=1)
index 23a02e0b4eb246dec304e527e1e4f33cfa0221fa..888586840f2c36b3eaba0bce769a6335cf9b9d16 100644 (file)
@@ -663,6 +663,38 @@ class BaseSemaphoreTests(BaseTestCase):
         b.wait_for_finished()
         self.assertEqual(sem_results, [True] * (6 + 7 + 6 + 1))
 
+    def test_multirelease(self):
+        sem = self.semtype(7)
+        sem.acquire()
+        results1 = []
+        results2 = []
+        phase_num = 0
+        def f():
+            sem.acquire()
+            results1.append(phase_num)
+            sem.acquire()
+            results2.append(phase_num)
+        b = Bunch(f, 10)
+        b.wait_for_started()
+        while len(results1) + len(results2) < 6:
+            _wait()
+        self.assertEqual(results1 + results2, [0] * 6)
+        phase_num = 1
+        sem.release(7)
+        while len(results1) + len(results2) < 13:
+            _wait()
+        self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7)
+        phase_num = 2
+        sem.release(6)
+        while len(results1) + len(results2) < 19:
+            _wait()
+        self.assertEqual(sorted(results1 + results2), [0] * 6 + [1] * 7 + [2] * 6)
+        # The semaphore is still locked
+        self.assertFalse(sem.acquire(False))
+        # Final release, to let the last thread finish
+        sem.release()
+        b.wait_for_finished()
+
     def test_try_acquire(self):
         sem = self.semtype(2)
         self.assertTrue(sem.acquire(False))
index 32a3d7c3033621f0604cfea91726e3d3a44f1748..c3bbb19acbe2247a403a4067e41d747becd0fac4 100644 (file)
@@ -439,16 +439,19 @@ class Semaphore:
 
     __enter__ = acquire
 
-    def release(self):
-        """Release a semaphore, incrementing the internal counter by one.
+    def release(self, n=1):
+        """Release a semaphore, incrementing the internal counter by one or more.
 
         When the counter is zero on entry and another thread is waiting for it
         to become larger than zero again, wake up that thread.
 
         """
+        if n < 1:
+            raise ValueError('n must be one or more')
         with self._cond:
-            self._value += 1
-            self._cond.notify()
+            self._value += n
+            for i in range(n):
+                self._cond.notify()
 
     def __exit__(self, t, v, tb):
         self.release()
@@ -475,8 +478,8 @@ class BoundedSemaphore(Semaphore):
         Semaphore.__init__(self, value)
         self._initial_value = value
 
-    def release(self):
-        """Release a semaphore, incrementing the internal counter by one.
+    def release(self, n=1):
+        """Release a semaphore, incrementing the internal counter by one or more.
 
         When the counter is zero on entry and another thread is waiting for it
         to become larger than zero again, wake up that thread.
@@ -485,11 +488,14 @@ class BoundedSemaphore(Semaphore):
         raise a ValueError.
 
         """
+        if n < 1:
+            raise ValueError('n must be one or more')
         with self._cond:
-            if self._value >= self._initial_value:
+            if self._value + n > self._initial_value:
                 raise ValueError("Semaphore released too many times")
-            self._value += 1
-            self._cond.notify()
+            self._value += n
+            for i in range(n):
+                self._cond.notify()
 
 
 class Event:
diff --git a/Misc/NEWS.d/next/Library/2019-08-29-01-19-13.bpo-10978.J6FQYY.rst b/Misc/NEWS.d/next/Library/2019-08-29-01-19-13.bpo-10978.J6FQYY.rst
new file mode 100644 (file)
index 0000000..2b8f300
--- /dev/null
@@ -0,0 +1,2 @@
+Semaphores and BoundedSemaphores can now release more than one waiting
+thread at a time.