If you're using CPython1, you can do this without explicit locks:
import itertools
class Counter:
def __init__(self):
self._incs = itertools.count()
self._accesses = itertools.count()
def increment(self):
next(self._incs)
def value(self):
return next(self._incs) - next(self._accesses)
my_global_counter = Counter()
We need two counters: one to count increments and one to count accesses of value()
. This is because itertools.count
does not provide a way to access the current value, only the next value. So we need to "undo" the increments we incur just by asking for the value.
This is threadsafe because itertools.count.__next__()
is atomic in CPython (thanks, GIL!) and we don't persist the difference.
Note that if value()
is accessed in parallel, the exact number may not be perfectly stable or strictly monotonically increasing. It could be plus or minus a margin proportional to the number of threads accessing. In theory, self._incs
could be updated first in one thread while self._accesses
is updated first in another thread. But overall the system will never lose any data due to unguarded writes; it will always settle to the correct value.
1 Not all Python is CPython, but a lot (most?) is.
2 Credit to https://julien.danjou.info/atomic-lock-free-counters-in-python/ for the initial idea to use itertools.count
to increment and a second access counter to correct. They stopped just short of removing all locks.