Skip to main content

Atomic lock-free primitives

Project description

atomics

This library implements a wrapper around the lower level patomic C library (which is provided as part of this library through the build_patomic command in setup.py).

It exposes hardware level lock-free (and address-free) atomic operations on a memory buffer, either internally allocated or externally provided, via a set of atomic classes.

The operations in these classes are both thread-safe and process-safe, meaning that they can be used on a shared memory buffer for interprocess communication (including with other languages such as C/C++).

Table of Contents

Installing

Linux/MacOS:

$ python3 -m pip install atomics

Windows:

$ py -m pip install atomics

This library requires Python3.6+, and has a dependency on the cffi library. While the code here has no dependency on any implementation specific features, the cffi library functions used are likely to not work outside of CPython and PyPy.

Binaries are provided for the following platforms:

  • Windows [x86, amd64]
  • MacOSX [x86_64, universal2]
  • Linux [i686, x86_64, aarch64, ppc64le, s390x] [manylinux2014, musllinux_1_1]
  • Linux [i686, x86_64] [manylinux1]

If you are on one of these platforms and pip tries to build from source or fails to install, make sure that you have the latest version of pip installed. This can be done like so:

Linux/MacOS:

$ python3 -m pip install --upgrade pip

Windows:

$ py -m pip install --upgrade pip

If you need to build from source, check out the Building section as there are additional requirements for that.

Examples

Incorrect

The following example has a data race (ais modified from multiple threads). The program is not correct, and a's value will not equal total at the end.

from threading import Thread


a = 0


def fn(n: int) -> None:
    global a
    for _ in range(n):
        a += 1


if __name__ == "__main__":
    # setup
    total = 10_000_000
    # run threads to completion
    t1 = Thread(target=fn, args=(total // 2,))
    t2 = Thread(target=fn, args=(total // 2,))
    t1.start(), t2.start()
    t1.join(), t2.join()
    # print results
    print(f"a[{a}] != total[{total}]")

Multi-Threading

This example implements the previous example but a is now an AtomicInt which can be safely modified from multiple threads (as opposed to int which can't). The program is correct, and a will equal total at the end.

import atomics
from threading import Thread


def fn(ai: atomics.INTEGRAL, n: int) -> None:
    for _ in range(n):
        ai.inc()


if __name__ == "__main__":
    # setup
    a = atomics.atomic(width=4, atype=atomics.INT)
    total = 10_000
    # run threads to completion
    t1 = Thread(target=fn, args=(a, total // 2))
    t2 = Thread(target=fn, args=(a, total // 2))
    t1.start(), t2.start()
    t1.join(), t2.join()
    # print results
    print(f"a[{a.load()}] == total[{total}]")

Multi-Processing

This example is the counterpart to the above correct code, but using processes to demonstrate that atomic operations are also safe across processes. This program is also correct, and a will equal total at the end. It is also how one might communicate with processes written in other languages such as C/C++.

import atomics
from multiprocessing import Process, shared_memory


def fn(shmem_name: str, width: int, n: int) -> None:
    shmem = shared_memory.SharedMemory(name=shmem_name)
    buf = shmem.buf[:width]
    with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:
        for _ in range(n):
            a.inc()
    del buf
    shmem.close()


if __name__ == "__main__":
    # setup
    width = 4
    shmem = shared_memory.SharedMemory(create=True, size=width)
    buf = shmem.buf[:width]
    total = 10_000
    # run processes to completion
    p1 = Process(target=fn, args=(shmem.name, width, total // 2))
    p2 = Process(target=fn, args=(shmem.name, width, total // 2))
    p1.start(), p2.start()
    p1.join(), p2.join()
    # print results and cleanup
    with atomics.atomicview(buffer=buf, atype=atomics.INT) as a:
        print(f"a[{a.load()}] == total[{total}]")
    del buf
    shmem.close()
    shmem.unlink()

NOTE: Although shared_memory is showcased here, atomicview accepts any type that supports the buffer protocol as its buffer argument, so other sources of shared memory such as mmap could be used instead.

Docs

Types

The following helper (abstract-ish base) types are available in atomics:

  • [ANY, INTEGRAL, BYTES, INT, UINT]

This library provides the following Atomic classes in atomics.base:

  • Atomic --- ANY
  • AtomicIntegral --- INTEGRAL
  • AtomicBytes --- BYTES
  • AtomicInt --- INT
  • AtomicUint --- UINT

These Atomic classes are constructable on their own, but it is strongly suggested using the atomic() function to construct them. Each class corresponds to one of the above helper types (as indicated).

This library also provides Atomic*View (in atomics.view) and Atomic*ViewContext (in atomics.ctx) counterparts to the Atomic* classes, corresponding to the same helper types.

The latter of the two sets of classes can be constructed manually, although it is strongly suggested using the atomicview() function to construct them. The former set of classes cannot be constructed manually with the available types, and should only be obtained by called .__enter__() on a corresponding Atomic*ViewContext object.

Even though you should never need to directly use these classes (apart from the helper types), they are provided to be used in type hinting. The inheritance hierarchies are detailed in the ARCHITECTURE.md file (available on GitHub).

Construction

This library provides the functions atomic and atomicview, along with the types BYTES, INT, and UINT (as well as ANY and INTEGRAL) to construct atomic objects like so:

import atomics

a = atomics.atomic(width=4, atype=atomics.INT)
print(a)  # AtomicInt(value=0, width=4, readonly=False, signed=True)

buf = bytearray(2)
with atomics.atomicview(buffer=buf, atype=atomics.BYTES) as a:
    print(a)  # AtomicBytesView(value=b'\x00\x00', width=2, readonly=True)

You should only need to construct objects with an atype of BYTES, INT, or UINT. Using an atype of ANY or INTGERAL will require additional kwargs, and an atype of ANY will result in an object that doesn't actually expose any atomic operations (only properties, explained in sections further on).

The atomic() function returns a corresponding Atomic* object.

The atomicview() function returns a corresponding Atomic*ViewContext object. You can use this context object in a with statement to obtain an Atomic*View object. The buffer parameter may be any object that supports the buffer protocol.

Construction can raise UnsupportedWidthException and AlignmentError.

NOTE: the width property of Atomic*View objects is derived from the buffer's length as if it were contiguous. It is equivalent to calling memoryview(buf).nbytes.

Lifetime

Objects of Atomic* classes (i.e. objects returned by the atomic() function) have a self-contained buffer which is automatically freed. They can be passed around and stored liked regular variables, and there is nothing special about their lifetime.

Objects of Atomic*ViewContext classes (i.e. objects returned by the atomicview() function) and Atomic*View objects obtained from said objects have a much stricter usage contract.

Contract

The buffer used to construct an Atomic*ViewContext object (either directly or through atomicview()) MUST NOT be invalidated until .release() is called. This is aided by the fact that .release() is called automatically in .__exit__(...) and .__del__(). As long as you immediately use the context object in a with statement, and DO NOT invalidate the buffer inside that with scope, you will always be safe.

The protections implemented are shown in this example:

import atomics


buf = bytearray(4)
ctx = atomics.atomicview(buffer=buf, atype=atomics.INT)

# ctx.release() here will cause ctx.__enter__() to raise:
# ValueError("Cannot open context after calling 'release'.")

with ctx as a:  # this calls ctx.__enter__()
    # ctx.release() here will raise:
    # ValueError("Cannot call 'release' while context is open.")

    # ctx.__enter__() here will raise:
    # ValueError("Cannot open context multiple times.")
    
    print(a.load())  # ok

# ctx.__exit__(...) now called
# we can safely invalidate object 'buf' now

# ctx.__enter__() will raise:
# ValueError("Cannot open context after calling 'release'.")

# accessing object 'a' in any way will also raise an exception

Furthermore, in CPython, all built-in types supporting the buffer protocol will throw a BufferError exception if you try to invalidate them while they're in use (i.e. before calling .release()).

As a last resort, if you absolutely must invalidate the buffer inside the with context (where you can't call .release()), you may call .__exit__(...) manually on the Atomic*ViewContext object. This is to force explicitness about something considered to be bad practice and dangerous.

Where it's allowed, .release() may be called multiple times with no ill-effects. This also applies to .__exit__(...), which has no restrictions on where it can be called.

Alignment

Different platforms may each have their own alignment requirements for atomic operations of given widths. This library provides the Alignment class in atomics to ensure that a given buffer meets these requirements.

from atomics import Alignment

buf = bytearray(8)
align = Alignment(len(buf))
assert align.is_valid(buf)

If an atomic class is constructed from a misaligned buffer, the constructor will raise AlignmentError.

By default, .is_valid calls .is_valid_recommended. The class Alignment also exposes .is_valid_minimum. Currently, no atomic class makes use of the minimum alignment, so checking for it is pointless. Support for it will be added in a future release.

Properties

All Atomic* and Atomic*View classes have the following properties:

  • width: width in bytes of the underlying buffer (as if it were contiguous)
  • readonly: whether the object supports modifying operations
  • ops_supported: a sorted list of OpType enum values representing which operations are supported on the object

Integral Atomic* and Atomic*View classes also have the following property:

  • signed: whether arithmetic operations are signed or unsigned

In both cases, the behaviour on overflow is defined to wraparound.

Operations

Base Atomic and AtomicView objects (corresponding to ANY) expose no atomic operations.

AtomicBytes and AtomicBytesView objects support the following operations:

  • [base]: load, store
  • [xchg]: exchange, cmpxchg_weak, cmpxchg_strong
  • [bitwise]: bit_test, bit_compl, bit_set, bit_reset
  • [binary]: bin_or, bin_xor, bin_and, bin_not
  • [binary]: bin_fetch_or, bin_fetch_xor, bin_fetch_and, bin_fetch_not

Integral Atomic* and Atomic*View classes additionally support the following operations:

  • [arithmetic]: add, sub, inc, dec, neg
  • [arithmetic]: fetch_add, fetch_sub, fetch_inc, fetch_dec, fetch_neg

The usage of (most of) these functions is modelled directly on the C++11 std::atomic implementation found here.

Compare Exchange (cmpxchg_*)

The cmpxchg_* functions return CmpxchgResult. This has the attributes .success: bool which indicates whether the exchange took place, and .expected: T which holds the original value of the atomic object.
The cmpxchg_weak function may fail spuriously, even if expected matches the actual value. It should be used as shown below:

import atomics


def atomic_mul(a: atomics.INTEGRAL, operand: int):
    res = atomics.CmpxchgResult(success=False, expected=a.load())
    while not res:
        desired = res.expected * operand
        res = a.cmpxchg_weak(expected=res.expected, desired=desired)

In a real implementation of atomic_mul, care should be taken to ensure that desired fits in a (i.e. desired.bit_length() < (a.width * 8), assuming 8 bits in a byte).

Exceptions

All operations can raise UnsupportedOperationException (so check .ops_supported if you need to be sure).

Operations load, store, and cmpxchg_* can raise MemoryOrderError if called with an invalid memory order. MemoryOrder enum values expose the functions is_valid_store_order(), is_valid_load_order(), and is_valid_fail_order() to check with.

Special Methods

AtomicBytes and AtomicBytesView implement the __bytes__ special method.

Integral Atomic* and Atomic*View classes implement the __int__ special method. They intentionally do not implement __index__.

There is a notable lack of any classes implementing special methods corresponding to atomic operations; this is intentional. Assignment in Python is not available as a special method, and we do not want to encourage people to use other special methods with this class, lest it lead to them accidentally using assignment when they meant .store(...).

Memory Order

The MemoryOrder enum class is provided in atomics, and the memory orders are directly copied from C++11's std::memory_order documentation found here, except for CONSUME (which would be pointless to expose in this library).

All operations have a default memory order, SEQ_CST. This will enforce sequential consistency, and essentially make your multi-threaded and/or multi-processed program be as correct as if it were to run in a single thread.

IF YOU DO NOT UNDERSTAND THE LINKED DOCUMENTATION, DO NOT USE YOUR OWN MEMORY ORDERS!!! Stick with the defaults to be safe. (And realistically, this is Python, you won't get a noticeable performance boost from using a more lax memory order).

The following helper functions are provided:

  • .is_valid_store_order() (for store op)
  • .is_valid_load_order() ( for load op)
  • .is_valid_fail_order() (for the fail ordering in cmpxchg_* ops)

Passing an invalid memory order to one of these ops will raise MemoryOrderError.

Exceptions

The following exceptions are available in atomics.exc:

  • AlignmentError
  • MemoryOrderError
  • UnsupportedWidthException
  • UnsupportedOperationException

Building

IMPORTANT: Make sure you have the latest version of pip installed.

Using setup.py's build or bdist_wheel commands will run the build_patomic command (which you can also run directly).

This clones the patomic library into a temporary directory, builds it, and then copies the shared library into atomics._clib.

This requires that git be installed on your system (a requirement of the GitPython module). You will also need an ANSI/C90 compliant C compiler (although ideally a more recent compiler should be used). CMake is also required but should be automatically pip install'd if not available.

If you absolutely cannot get build_patomic to work, go to patomic, follow the instructions on building it (making sure to build the shared library version), and then copy-paste the shared library file into atomics._clib manually.

NOTE: Currently, the library builds a dummy extension in order to trick setuptools into building a non-purepython wheel. If you are ok with a purepython wheel, then feel free to remove the code for that from setup.py (at the bottom).
Otherwise, you will need a C99 compliant C compiler, and probably the development libraries/headers for whichever version of Python you're using.

Future Thoughts

  • add docstrings
  • add tests
  • add support for minimum alignment
  • add support for constructing Atomic classes' buffers in shared memory
  • add support for passing Atomic objects to sub-processes and sub-interpreters
  • reimplement in C or Cython for performance gains (preliminary benchmarks put such implementations at 2x the speed of a raw int)

Contributing

I don't have a guide for contributing yet. This section is here to make the following two points:

  • new operations must first be implemented in patomic before this library can be updated
  • new architectures, widths, and existing unsupported operations must be supported in patomic (no change required in this library)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

atomics-1.0.3.tar.gz (44.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_arm64.whl (61.2 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9Windows ARM64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_amd64.whl (62.4 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9Windows x86-64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win32.whl (59.1 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9Windows x86

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_x86_64.whl (185.7 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9musllinux: musl 1.2+ x86-64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_s390x.whl (217.2 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9musllinux: musl 1.2+ s390x

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_ppc64le.whl (200.3 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9musllinux: musl 1.2+ ppc64le

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_i686.whl (162.2 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9musllinux: musl 1.2+ i686

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_aarch64.whl (232.5 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9musllinux: musl 1.2+ ARM64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_s390x.manylinux2014_s390x.whl (218.9 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9manylinux: glibc 2.17+ s390x

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl (202.9 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9manylinux: glibc 2.17+ ppc64le

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (237.2 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9manylinux: glibc 2.17+ ARM64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (191.5 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9manylinux: glibc 2.17+ x86-64manylinux: glibc 2.5+ x86-64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl (193.0 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9manylinux: glibc 2.17+ i686manylinux: glibc 2.5+ i686

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_11_0_arm64.whl (71.2 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9macOS 11.0+ ARM64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_x86_64.whl (70.3 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9macOS 10.9+ x86-64

atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_universal2.whl (71.5 kB view details)

Uploaded Python 3.10Python 3.11Python 3.6Python 3.7Python 3.8Python 3.9macOS 10.9+ universal2 (ARM64, x86-64)

File details

Details for the file atomics-1.0.3.tar.gz.

File metadata

  • Download URL: atomics-1.0.3.tar.gz
  • Upload date:
  • Size: 44.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.0.1 CPython/3.12.8

File hashes

Hashes for atomics-1.0.3.tar.gz
Algorithm Hash digest
SHA256 97cb3f7514c1ac6cdf8900891565327b03bcf93e443989433ae0fe3a03187954
MD5 98bb7f25aa6a6a1aedce13008bd00da7
BLAKE2b-256 e865240802d5c149fbd3ef08f322fc9b072fb1176b6bfca4ed8b16e611998de0

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_arm64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_arm64.whl
Algorithm Hash digest
SHA256 0dd94388b3de1a9191a360d293e14d300ad89d5740801ea91362c5ceeb50de37
MD5 4a14c336151326f88096312dcef8ffed
BLAKE2b-256 09cdc0faecd5482be2cd9e20882478802557be1f69e84f62b8af01fb108b47db

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_amd64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win_amd64.whl
Algorithm Hash digest
SHA256 8e9b4d07026e6363d385cf8be767d1f35338e3c63fd44ec1e742734406b5f87d
MD5 889a82cfe283115409260217c0b019aa
BLAKE2b-256 b66e02eee4fde6ff5fccedbbff8a6b390d32a974aaa1d4518c760a1f4736b55a

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win32.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-win32.whl
Algorithm Hash digest
SHA256 b0998c0b956c85f2b135f343386359cf63249dc9508e555147fa9a856d42010c
MD5 4b5b56524d81596e0c824322bd91461e
BLAKE2b-256 691cb283c14acc0145731e92c6fa86098a6be96fd831680f5695c06fc65f497d

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 7d2e931185ba9fc541b3d2ab0a4c317e16cf57c4246a6842d38764c144a47304
MD5 b1762b17f552940cd9348f113eec59d8
BLAKE2b-256 db6fefa448f77bfb4d4c53fe56cbaba98b61480b789da9139e6836df236417c5

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_s390x.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_s390x.whl
Algorithm Hash digest
SHA256 944d37eff284ea582ad07b0fb06c030d0c82cfabb27a807c5f6abe72a5d93dca
MD5 5d5b7c62aa61a902c74fd3d47e307fdf
BLAKE2b-256 52f686d7c6753647c66ef5103001a0cd6d96eba9f035b6c5df01dea918100a71

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_ppc64le.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_ppc64le.whl
Algorithm Hash digest
SHA256 c67ea8557d6214656400f8ea2188525677bdb57a91649037fba944f391104bd5
MD5 c539edaac0f30a1f55cc35f15fb9f5ad
BLAKE2b-256 47275df3af055614b5695e3832fc6b5e936b4e9b4f86ad15b682e26175cedf0b

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 bcc1227b5b25fd1770738f6183201456b3777460c9e93f8027e5116c846a951e
MD5 8cc39e1835d5d3a2cb51cb6549340435
BLAKE2b-256 3991c466393543396cef27b8277e18c667be094068b59cc42ec1f09acfd73b76

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 5392357c9da521d7618e6514913cda364fa3314a60c8eccedbeed197616f74f6
MD5 203fd8f797d7d28d87b727df8e01c092
BLAKE2b-256 463f6a6acb609f23d9836c472ec3c78e05bab5fb406001adfac890cdc3aef086

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_s390x.manylinux2014_s390x.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_s390x.manylinux2014_s390x.whl
Algorithm Hash digest
SHA256 19b4deec2c236d9bd81a2ffa4e45514c64663d5c50a2b028f5b146e209110f39
MD5 a9f03dd3565742adf05e163ae506931d
BLAKE2b-256 8377f8251d38d52f9c16c5c5e028afc453f5022b131b3cedf904ba22ac5edd5b

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
Algorithm Hash digest
SHA256 ae378312bf3c7cd978e0322dadfb15582995494ad8bc1195713210491291b1ec
MD5 250dd4217600e3deabd3cd616f8ef2f2
BLAKE2b-256 988cdb21c91987bbaf73194c00fc6ef134cc7ff5f9980bc6abee500bb6ae3ac2

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 53d5e875baf490b0e5d06578076758bd1257223b6bf4c76969034ffab4e9e5a8
MD5 f52146ee68f10e9874947fa63483cbe3
BLAKE2b-256 b3d7eebadf02b8ec5779abced24d0cdd53ed092dcfdbcbd1e0ead95d4c25d563

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 19ed27f1ae3fe3353e9103e6d6f54af83e56a929f78ba061ae2e8e9435101daa
MD5 258f8cbeedbb47b8512c849f1ff9299b
BLAKE2b-256 c3ad7844f3e4e33cd3d770dd66dd8c0d091e5ff81851235ff4ccbbda760e7264

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 0d3b5e05d351e3166088a931d039a953adc2a31a6f75ca553ec32b7b7ce372e3
MD5 07189aa7459ae252ab2afd3f077c919c
BLAKE2b-256 c8150a881091d36e7b0a536cff684b3b45708940c7d58be4f872b1b79fa01923

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 d32c51d74d2f8ffc4554b1431d51c2261493bb7a338dc98dd669c356e1b493dd
MD5 7efecc1755920b874db27b7d7c6b0af6
BLAKE2b-256 a9ed4086d2b88ff42fd756f38d82d78d45ed0faa21dbabbd7eb93cf166e57ebd

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 a4262ca6b5cbe167c30ebabc23cca0607c20c6e5293b8555ffbbeb020ecc850b
MD5 6c445856c0b6be30151b243b26a80124
BLAKE2b-256 214fdaed7e3bab85a5d214a17feba7764e90ae5d9be9002780dbf7df49a93397

See more details on using hashes here.

File details

Details for the file atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_universal2.whl.

File metadata

File hashes

Hashes for atomics-1.0.3-py36.py37.py38.py39.py310.py311-none-macosx_10_9_universal2.whl
Algorithm Hash digest
SHA256 4a6b7bec9525683e128774916e045462ee9fae848c0fa416692c7ff7e2a7f032
MD5 e7b9f31edf36c780dc0d2232ce9dc068
BLAKE2b-256 8de7cbc923d1eaf4d922bbc1cf8b76edb18cbfcbcebdf6b6be5f14eee24fc3c0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page