I had to implement a simple atomic counter the other day to count the total number of requests processed in a multi-threaded Python web server. I was doing a demo of "how cool Python is" to my colleagues, and they were generally wowed, but one of the things that made them do a double-take (coming mostly from Scala/Java) was that there was no atomic counter in the standard library. The fact that I and many other folks have implemented such things makes me wonder if it should be in the standard library. It's pretty simple to implement, basically the handful of lines of code below (full version on GitHub Gist at https://gist.github.com/benhoyt/8c8a8d62debe8e5aa5340373f9c509c7): import threading class AtomicCounter: def __init__(self, initial=0): self.value = initial self._lock = threading.Lock() def increment(self, num=1): with self._lock: self.value += num return self.value And if you just want a one-off and don't want to write a class, it's like so: import threading counter_lock = threading.Lock() counter = 0 with counter_lock: counter += 1 value = counter print(value) But it could be this much more obvious code: import threading counter = threading.AtomicCounter() value = counter.increment() print(value) Thoughts? Would such a class make a good candidate for the standard library? (API could probably be improved.) -Ben
On 25.08.2016 19:31, Ben Hoyt wrote:
I had to implement a simple atomic counter the other day to count the total number of requests processed in a multi-threaded Python web server.
I was doing a demo of "how cool Python is" to my colleagues, and they were generally wowed, but one of the things that made them do a double-take (coming mostly from Scala/Java) was that there was no atomic counter in the standard library.
The fact that I and many other folks have implemented such things makes me wonder if it should be in the standard library.
As long as Python uses a GIL to protect C level function calls, you can use an iterator for this: import itertools x = itertools.count() ... mycount = next(x) ... The trick here is that the CALL_FUNCTION byte code will trigger the increment of the iterator. Since this is implemented in C, the GIL will serve as lock on the iterator while it is being incremented. With Python 4.0, this will all be different, though :-) -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Aug 25 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
As long as Python uses a GIL to protect C level function calls, you can use an iterator for this:
import itertools x = itertools.count() ... mycount = next(x)
Yeah, that's a neat hack -- I saw it recommended on StackOverflow, and saw it used in the standard library somewhere. I think that's probably okay in the *CPython* stdlib, because it's CPython so you know it has the GIL. But this wouldn't work in other Python implementations, would it (IronPython and Jython don't have a GIL). Or when itertools.count() is implemented in pure Python on some system? Seems like it could blow up in someone's face when they're least expecting it. I also think using *iter*tools is a pretty non-obvious way to get a thread-safe counter. -Ben
The only reason I can think of for wanting this simple class in the stdlib would be that on GIL-free Python implementations you could replace it with a lock-free version. But I think we'd probably want to rethink a bunch of other datastructures to be lock-free, and a 3rd party package on PyPI makes more sense to me than jumping right into the stdlib. On Thu, Aug 25, 2016 at 11:10 AM, Ben Hoyt <benhoyt@gmail.com> wrote:
As long as Python uses a GIL to protect C level function
calls, you can use an iterator for this:
import itertools x = itertools.count() ... mycount = next(x)
Yeah, that's a neat hack -- I saw it recommended on StackOverflow, and saw it used in the standard library somewhere. I think that's probably okay in the *CPython* stdlib, because it's CPython so you know it has the GIL. But this wouldn't work in other Python implementations, would it (IronPython and Jython don't have a GIL). Or when itertools.count() is implemented in pure Python on some system? Seems like it could blow up in someone's face when they're least expecting it. I also think using *iter*tools is a pretty non-obvious way to get a thread-safe counter.
-Ben
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- --Guido van Rossum (python.org/~guido)
On 25.08.2016 20:10, Ben Hoyt wrote:
As long as Python uses a GIL to protect C level function calls, you can use an iterator for this:
import itertools x = itertools.count() ... mycount = next(x)
Yeah, that's a neat hack -- I saw it recommended on StackOverflow, and saw it used in the standard library somewhere. I think that's probably okay in the *CPython* stdlib, because it's CPython so you know it has the GIL. But this wouldn't work in other Python implementations, would it (IronPython and Jython don't have a GIL). Or when itertools.count() is implemented in pure Python on some system? Seems like it could blow up in someone's face when they're least expecting it. I also think using *iter*tools is a pretty non-obvious way to get a thread-safe counter.
All true. Having an implementation in threading which hides away the details would be nice. On CPython using iterators would certainly be one of the most efficient ways of doing this. -- Marc-Andre Lemburg eGenix.com Professional Python Services directly from the Experts (#1, Aug 25 2016)
Python Projects, Coaching and Consulting ... http://www.egenix.com/ Python Database Interfaces ... http://products.egenix.com/ Plone/Zope Database Interfaces ... http://zope.egenix.com/
::: We implement business ideas - efficiently in both time and costs ::: eGenix.com Software, Skills and Services GmbH Pastor-Loeh-Str.48 D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg Registered at Amtsgericht Duesseldorf: HRB 46611 http://www.egenix.com/company/contact/ http://www.malemburg.com/
import threading
class AtomicCounter: def __init__(self, initial=0): self.value = initial self._lock = threading.Lock()
def increment(self, num=1): with self._lock: self.value += num return self.value
Some late nitpicking, sorry: This is not an atomic counter, it's a thread-safe counter. And since being thread-safe is rarely a bad idea, especially assuming someone will manage to Kill GIL, I would just call it a "counter" or Counter. (Of course, atomic/lock-free implementations are nice when possible.) -- Koos
participants (4)
-
Ben Hoyt
-
Guido van Rossum
-
Koos Zevenhoven
-
M.-A. Lemburg