This library is a thin wrapper for std::atomic
library in C++
Lately we have worked with Python multithreading and multiprocessing a lot. As natural, synchronization is needed when doing some stuffs. However, Python only provides primitive technique like Lock
to synchronize value, and Lock
(s) are bad.
Some may think it is ok to sacrify performance for locks. Howevers, when looking at other languages, such as C++
, Java
, we found that we can sacrify performance for synchronization, but do it efficiently with atomic
operations.
Because of that, by simply binding atomic
mechanism from C++ to Python using Cython, we can achieve efficient performance trade-off for synchronization. Just look at the Performance benchmark for more insight.
Currently we only have Linux wheel, for other platform, better install it from source
python3 -m pip install pytomic
- Make sure you have
Cython >= 0.29.30
, if you dont, install it:python3 -m pip install Cython=0.29.30
- Clone this repository:
git clone git@github.com:ThamZoo/pytomic.git
- Install the library:
cd pytomic && python3 -m pip install .
pytomic
comes with multithreading
and multiprocessing
variant, depends on your use case. multiprocessing
variant can also be used in multithreading
environment.
Basically, imported classes Atomic[Int | UInt | IntUnsafe | UIntUnsafe]
have the same methods as list below:
store(val: int) -> None
: atomically store the valueload() -> int
: atomically read the valuepreinc() -> int
: same as++atomic
in C++postinc() -> int
: same asatomic++
in C++predec() -> int
: same as--atomic
in C++postdec() -> int
: same asatomic++
in C++store_relaxed(val: int) -> None
:store
withmemory_order_relaxed
load_relaxed() -> int
:load
withmemory_order_relaxed
compare_exchange_weak(expected: int, desired: int) -> bool
: same ascompare_exchange_weak(T&, T)
in C++compare_exchange_strong(expected: int, desired: int) -> bool
: same ascompare_exchange_strong(T&, T)
in C++
from multiprocessing import Process
from pytomic.multiprocessing import AtomicUInt
# If you are using multithreading, import this instead
# from pytomic.multithreading import AtomicUInt
NUM_INC = int(1e5)
NUM_PROC = 4
atomic = AtomicUInt(0)
def atomic_inc():
global atomic
for i in range(NUM_INC):
atomic.preinc()
def run(target):
procs = []
for i in range(NUM_PROC):
p = Process(target=target)
p.start()
procs.append(p)
for p in procs:
p.join()
print("Value before run: ", atomic.load())
run(atomic_inc)
print("Value after run: ", atomic.load())
If you are using safe classes Atomic[Int | UInt]
, you will receive OverflowError
exception when trying to do out-of-bound operations. This is likely a defensive mechanism to prevent errors that are really hard to debug. However, this comes with the cost of constantly checking if the value is out-of-bound.
If you want more speed, and ensure that the value will not out-of-bound, you can use unsafe classes Atomic[Int | UInt]Unsafe
, those classes will not raise OverflowError
except when you call store()
method with out-of-bound value.
Maxium value constant can be imported from the library: from pytomic.cpp_limits import C_MAX_INT, C_MIN_INT, C_MAX_UINT
Multithreading:
$ python3 __bench__/bench_multithreading.py
lock_inc solution | num_threads = 4 | inc_count = 100,000 | repeat = 5 | done after 65.2207 | avg ops = 30,665 ops/s
atomic_inc solution | num_threads = 4 | inc_count = 100,000 | repeat = 5 | done after 0.1490 | avg ops = 13,426,450 ops/s
atomic_unsafe_inc solution | num_threads = 4 | inc_count = 100,000 | repeat = 5 | done after 0.1367 | avg ops = 14,632,044 ops/s
Multiprocessing:
$ python3 __bench__/bench_multiprocessing.py
lock_inc solution | num_procs = 4 | inc_count = 100,000 | repeat = 5 | done after 21.8647 | avg ops = 91,472 ops/s
atomic_inc solution | num_procs = 4 | inc_count = 100,000 | repeat = 5 | done after 0.1118 | avg ops = 17,893,905 ops/s
atomic_unsafe_inc solution | num_procs = 4 | inc_count = 100,000 | repeat = 5 | done after 0.0896 | avg ops = 22,326,437 ops/s
- Currently, the library only provides atomicity for
int
type in Python. Support forfloat
,bool
,char
can be added later. - Operations like
wait
,notify
haven't ported to Python yet.
For who are curious, please review the code using the folder structure as follow:
- pytomic: Contains Cython binding for C++
std::atomic
- test: Test cases
- bench: Benchmark code
- test.sh and clean_ext.sh: Utilities scripts to build extension inplace and test; clear extension builds
We use pipenv
to create virtual environment. Pull this repo and python3 -m pipenv install --dev
to install all dependencies.
To test it: ./test.sh
or python3 -m pytest __test__/
To build the extension inplace: python3 setup.py build_ext -i