New submission from Garrett Berg <googb...@gmail.com>: The definition of threading.Semaphore is confusing (for all versions of python docs)
https://docs.python.org/2/library/threading.html#semaphore-objects acquire([blocking]) It currently states the following: > When invoked without arguments: if the internal counter is larger than zero > on entry, decrement it by one and return immediately. If it is zero on entry, > block, waiting until some other thread has called release() to make it larger > than zero. This is done with proper interlocking so that if multiple > acquire() calls are blocked, release() will wake exactly one of them up. The > implementation may pick one at random, so the order in which blocked threads > are awakened should not be relied on. There is no return value in this case. However, after testing it myself I found that is missing a crutial detail. Let's step through the docs: > If the internal counter is larger than zero on entry, decrement it by one and > return immediately. This is exactly accurate and should stay the same > If it is zero on entry, block, waiting until some other thread has called > release() to make it larger than zero. This is done with proper interlocking > so that if multiple acquire() calls are blocked, release() will wake exactly > one of them up. The implementation may pick one at random, so the order in > which blocked threads are awakened should not be relied on. There is no > return value in this case. This is extremely confusing and I would like to rewrite it as follows: > If it is zero on entry block until awoken by a call to ``release()``. Once > awoken, decrement the counter by 1. Exactly one thread will be awoken by a > call to ``release()``. The order in which threads are awoken should not be > relied on. ``None`` is returned in this case. The major thing that was missing was that the **counter is decremented after the thread is awoken**. For instance, this code *generally* passes assertions: ``` #!/usr/bin/python2 import time from threading import Thread, Semaphore s = Semaphore(1) def doit(): s.acquire() print("did it") th1 = Thread(target=doit) th1.start() th2 = Thread(target=doit) th2.start() time.sleep(0.2) assert not th1.is_alive() assert th2.is_alive() s.release() assert s._value == 1, "The value is increased to 1 MOMENTARILY" start = time.time() while sum([th2.is_alive(), th3.is_alive()]) > 1: assert time.time() - start < 0.5 time.sleep(0.1) assert s._value == 0, "when an aquire is awoken, THEN _value is decremented" ``` Obviously this behavior should not be relied on, but it gives a picture of what seems to be happening under the hood. I came across this while trying to work through "The Little Book of Semaphores". After reading these docs I mistakingly thought that they didn't match Djestra's original semaphore since the values could not be negative. I now realize that while they may not match that implementation under the hood, they match it perfectly in practice since if you (for instance) ``acquire()`` 5 times and then ``release()`` 5 times the value of Semaphore._value will be the same when all is said and done. ---------- components: Library (Lib) messages: 307536 nosy: Garrett Berg priority: normal severity: normal status: open title: Improve semaphore docmentation type: enhancement versions: Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7, Python 3.8 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue32208> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com