What is the difference between .Semaphore() and .BoundedSemaphore()?
Asked Answered
L

3

28

I know that threading.Lock() is equal to threading.Semaphore(1).

Is also threading.Lock() equal to threading.BoundedSemaphore(1) ?

And newly I saw threading.BoundedSemaphore(), what is the difference between them? For example in the following code snippet (applying limitation on threads):

import threading

sem = threading.Semaphore(5)
sem = threading.BoundedSemaphore(5)
Levey answered 25/2, 2018 at 7:8 Comment(0)
J
52

A Semaphore can be released more times than it's acquired, and that will raise its counter above the starting value. A BoundedSemaphore can't be raised above the starting value.

from threading import Semaphore, BoundedSemaphore

# Usually, you create a Semaphore that will allow a certain number of threads
# into a section of code. This one starts at 5.
s1 = Semaphore(5)

# When you want to enter the section of code, you acquire it first.
# That lowers it to 4. (Four more threads could enter this section.)
s1.acquire()

# Then you do whatever sensitive thing needed to be restricted to five threads.

# When you're finished, you release the semaphore, and it goes back to 5.
s1.release()


# That's all fine, but you can also release it without acquiring it first.
s1.release()

# The counter is now 6! That might make sense in some situations, but not in most.
print(s1._value)  # => 6

# If that doesn't make sense in your situation, use a BoundedSemaphore.

s2 = BoundedSemaphore(5)  # Start at 5.

s2.acquire()  # Lower to 4.

s2.release()  # Go back to 5.

try:
    s2.release()  # Try to raise to 6, above starting value.
except ValueError:
    print('As expected, it complained.')    
Jephthah answered 25/2, 2018 at 7:12 Comment(0)
L
2

The threading module provides the simple Semaphore class.

A Semaphore provides a non-bounded counter which allows you to call release() any number of times for incrementing.

However, to avoid programming errors, it’s usually a correct choice to use BoundedSemaphore , which raises an error if a release() call tries to increase the counter beyond its maximum size.

EDIT

A semaphore has an internal counter rather than a lock flag (in case of Locks), and it only blocks if more than a given number of threads have attempted to hold the semaphore. Depending on how the semaphore is initialized, this allows multiple threads to access the same code section simultaneously.

Laflamme answered 25/2, 2018 at 7:26 Comment(0)
R
1

I know that threading.Lock() is equal to threading.Semaphore(1).

Lock() is much faster. In my case (Python 3.10.12, @WSL, @Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz) it is ~15x faster:

$ python3 -m timeit "import threading; l=threading.Lock()" "l.acquire(); l.release()"
1000000 loops, best of 5: 230 nsec per loop
$ python3 -m timeit "import threading; l=threading.Semaphore(1)" "l.acquire(); l.release()"
100000 loops, best of 5: 3.64 usec per loop
$ python3 -m timeit "import threading; l=threading.BoundedSemaphore(1)" "l.acquire(); l.release()"
50000 loops, best of 5: 3.93 usec per loop
Retch answered 3/11, 2023 at 13:48 Comment(2)
good catch. so do you know what the reason is?Levey
I suppose it is because Semaphore needs to maintain its own counter, which probably needs some locking (see also source code: github.com/python/cpython/blob/main/Lib/threading.py#L454) , so there are more operaions to do to acquire/release a Semaphore than a simple mutex Lock (which seemingly is implemented in C here: github.com/python/cpython/blob/main/Modules/…).Retch

© 2022 - 2024 — McMap. All rights reserved.