├── MANIFEST.in ├── .gitignore ├── tox.ini ├── .travis.yml ├── README.rst ├── LICENSE ├── setup.py ├── atomiclong.py └── test_atomiclong.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include test_* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | .tox 4 | *.egg-info 5 | build 6 | dist 7 | *.so 8 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,pypy,py33 3 | 4 | [testenv] 5 | deps=pytest 6 | commands=py.test 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.3" 6 | - "pypy" 7 | 8 | matrix: 9 | allow_failures: 10 | - python: "pypy" 11 | 12 | install: "python setup.py install" 13 | script: "py.test -v" 14 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | AtomicLong 2 | ========== 3 | 4 | Sometimes you need to increment some numbers 5 | ... atomically 6 | ... in python. 7 | 8 | ``AtomicLong`` was born out of the need for fast thread-safe counters in python. 9 | 10 | It uses `CFFI`_ to bind `GCC's Atomic Builtins`_. 11 | 12 | Its value is a C ``long`` which can be incremented, decremented, and set 13 | atomically. It is inspired by Java's `java.util.concurrent.atomic.AtomicLong`_. 14 | 15 | Example:: 16 | 17 | >>> from atomiclong import AtomicLong 18 | >>> a = AtomicLong(0) 19 | >>> a += 1 20 | >>> a.value 21 | 1 22 | >>> a += 10 23 | >>> a.value 24 | 11 25 | >>> a.value = 1000 26 | >>> a.value 27 | 1000 28 | >>> a -= 100 29 | >>> a.value 30 | 900 31 | 32 | 33 | .. _GCC's Atomic Builtins: http://gcc.gnu.org/onlinedocs/gcc-4.3.5/gcc/Atomic-Builtins.html 34 | 35 | .. _CFFI: https://cffi.readthedocs.org 36 | 37 | .. _java.util.concurrent.atomic.AtomicLong: http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicLong.html 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is the MIT license. 2 | 3 | Copyright (c) 2013 David Reid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from setuptools import setup 4 | from setuptools.command.test import test as TestCommand 5 | 6 | class PyTest(TestCommand): 7 | def finalize_options(self): 8 | TestCommand.finalize_options(self) 9 | self.test_args = [] 10 | self.test_suite = True 11 | 12 | def run_tests(self): 13 | import pytest 14 | errno = pytest.main(self.test_args) 15 | sys.exit(errno) 16 | 17 | try: 18 | from atomiclong import ffi 19 | except ImportError: 20 | ext_modules=[] 21 | else: 22 | ext_modules=[ffi.verifier.get_extension()] 23 | 24 | with open('README.rst') as r: 25 | README = r.read() 26 | 27 | setup( 28 | name='atomiclong', 29 | version='0.1.1', 30 | author='David Reid', 31 | author_email='dreid@dreid.org', 32 | url='https://github.com/dreid/atomiclong', 33 | description="An AtomicLong type using CFFI.", 34 | long_description=README, 35 | license='MIT', 36 | py_modules=['atomiclong'], 37 | setup_requires=['cffi'], 38 | install_requires=['cffi'], 39 | tests_require=['pytest'], 40 | ext_modules=ext_modules, 41 | zip_safe=False, 42 | cmdclass={"test": PyTest}, 43 | ) 44 | -------------------------------------------------------------------------------- /atomiclong.py: -------------------------------------------------------------------------------- 1 | from cffi import FFI 2 | 3 | from functools import total_ordering 4 | 5 | ffi = FFI() 6 | 7 | ffi.cdef(""" 8 | long long_add_and_fetch(long *, long); 9 | long long_sub_and_fetch(long *, long); 10 | long long_bool_compare_and_swap(long *, long, long); 11 | """) 12 | 13 | lib = ffi.verify(""" 14 | long long_add_and_fetch(long *v, long l) { 15 | return __sync_add_and_fetch(v, l); 16 | }; 17 | 18 | long long_sub_and_fetch(long *v, long l) { 19 | return __sync_sub_and_fetch(v, l); 20 | }; 21 | 22 | long long_bool_compare_and_swap(long *v, long o, long n) { 23 | return __sync_bool_compare_and_swap(v, o, n); 24 | }; 25 | """) 26 | 27 | 28 | @total_ordering 29 | class AtomicLong(object): 30 | def __init__(self, initial_value): 31 | self._storage = ffi.new('long *', initial_value) 32 | 33 | def __repr__(self): 34 | return '<{0} at 0x{1:x}: {2!r}>'.format( 35 | self.__class__.__name__, id(self), self.value) 36 | 37 | @property 38 | def value(self): 39 | return self._storage[0] 40 | 41 | @value.setter 42 | def value(self, new): 43 | lib.long_bool_compare_and_swap(self._storage, self.value, new) 44 | 45 | def __iadd__(self, inc): 46 | lib.long_add_and_fetch(self._storage, inc) 47 | return self 48 | 49 | def __isub__(self, dec): 50 | lib.long_sub_and_fetch(self._storage, dec) 51 | return self 52 | 53 | def __eq__(self, other): 54 | if isinstance(other, AtomicLong): 55 | return self.value == other.value 56 | else: 57 | return self.value == other 58 | 59 | def __ne__(self, other): 60 | return not (self == other) 61 | 62 | def __lt__(self, other): 63 | if isinstance(other, AtomicLong): 64 | return self.value < other.value 65 | else: 66 | return self.value < other 67 | -------------------------------------------------------------------------------- /test_atomiclong.py: -------------------------------------------------------------------------------- 1 | from atomiclong import AtomicLong, ffi, lib 2 | 3 | def test_long_add_and_fetch(): 4 | l = ffi.new('long *', 0) 5 | assert lib.long_add_and_fetch(l, 1) == 1 6 | assert lib.long_add_and_fetch(l, 10) == 11 7 | 8 | def test_long_sub_and_fetch(): 9 | l = ffi.new('long *', 0) 10 | assert lib.long_sub_and_fetch(l, 1) == -1 11 | assert lib.long_sub_and_fetch(l, 10) == -11 12 | 13 | def test_long_bool_compare_and_swap(): 14 | l = ffi.new('long *', 0) 15 | assert lib.long_bool_compare_and_swap(l, 0, 10) == True 16 | assert lib.long_bool_compare_and_swap(l, 1, 20) == False 17 | 18 | def test_atomiclong_repr(): 19 | l = AtomicLong(123456789) 20 | assert '' in repr(l) 22 | 23 | def test_atomiclong_value(): 24 | l = AtomicLong(0) 25 | assert l.value == 0 26 | l.value = 10 27 | assert l.value == 10 28 | 29 | def test_atomiclong_iadd(): 30 | l = AtomicLong(0) 31 | l += 10 32 | assert l.value == 10 33 | 34 | def test_atomiclong_isub(): 35 | l = AtomicLong(0) 36 | l -= 10 37 | assert l.value == -10 38 | 39 | def test_atomiclong_eq(): 40 | l1 = AtomicLong(0) 41 | l2 = AtomicLong(1) 42 | l3 = AtomicLong(0) 43 | assert l1 == 0 44 | assert l1 != 1 45 | assert not (l2 == 0) 46 | assert not (l2 != 1) 47 | assert l1 == l3 48 | assert not (l1 != l3) 49 | assert l1 != l2 50 | assert not (l1 == l2) 51 | 52 | def test_atomiclong_ordering(): 53 | l1 = AtomicLong(0) 54 | l2 = AtomicLong(1) 55 | l3 = AtomicLong(0) 56 | 57 | assert l1 < l2 58 | assert l1 <= l2 59 | assert l1 <= l3 60 | assert l2 > l1 61 | assert l2 >= l3 62 | assert l2 >= l2 63 | 64 | assert l1 < 1 65 | assert l1 <= 0 66 | assert l1 <= 1 67 | assert l1 > -1 68 | assert l1 >= -1 69 | assert l1 >= 0 70 | 71 | 72 | 73 | --------------------------------------------------------------------------------