This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author Garrett Berg
Recipients Garrett Berg
Date 2017-12-04.03:17:57
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1512357480.91.0.213398074469.issue32208@psf.upfronthosting.co.za>
In-reply-to
Content
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.
History
Date User Action Args
2017-12-04 03:18:00Garrett Bergsetrecipients: + Garrett Berg
2017-12-04 03:18:00Garrett Bergsetmessageid: <1512357480.91.0.213398074469.issue32208@psf.upfronthosting.co.za>
2017-12-04 03:18:00Garrett Berglinkissue32208 messages
2017-12-04 03:17:57Garrett Bergcreate