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

Reply via email to