Movatterモバイル変換


[0]ホーム

URL:


Module atomic

std::sync

Moduleatomic 

1.0.0 ·Source
Expand description

Atomic types

Atomic types provide primitive shared-memory communication betweenthreads, and are the building blocks of other concurrenttypes.

This module defines atomic versions of a select number of primitivetypes, includingAtomicBool,AtomicIsize,AtomicUsize,AtomicI8,AtomicU16, etc.Atomic types present operations that, when used correctly, synchronizeupdates between threads.

Atomic variables are safe to share between threads (they implementSync)but they do not themselves provide the mechanism for sharing and follow thethreading model of Rust.The most common way to share an atomic variable is to put it into anArc (anatomically-reference-counted shared pointer).

Atomic types may be stored in static variables, initialized usingthe constant initializers likeAtomicBool::new. Atomic staticsare often used for lazy global initialization.

§Memory model for atomic accesses

Rust atomics currently follow the same rules asC++20 atomics, specifically the rulesfrom theintro.races section, without the “consume” memory ordering. SinceC++ uses an object-based memory model whereas Rust is access-based, a bit of translation workhas to be done to apply the C++ rules to Rust: whenever C++ talks about “the value of anobject”, we understand that to mean the resulting bytes obtained when doing a read. When the C++standard talks about “the value of an atomic object”, this refers to the result of doing anatomic load (via the operations provided in this module). A “modification of an atomic object”refers to an atomic store.

The end result isalmost equivalent to saying that creating ashared reference to one of theRust atomic types corresponds to creating anatomic_ref in C++, with theatomic_ref beingdestroyed when the lifetime of the shared reference ends. The main difference is that Rustpermits concurrent atomic and non-atomic reads to the same memory as those cause no issue in theC++ memory model, they are just forbidden in C++ because memory is partitioned into “atomicobjects” and “non-atomic objects” (withatomic_ref temporarily converting a non-atomic objectinto an atomic object).

The most important aspect of this model is thatdata races are undefined behavior. A data raceis defined as conflicting non-synchronized accesses where at least one of the accesses isnon-atomic. Here, accesses areconflicting if they affect overlapping regions of memory and atleast one of them is a write. (Acompare_exchange orcompare_exchange_weak that does notsucceed is not considered a write.) They arenon-synchronized if neither of themhappens-before the other, according to the happens-before order of the memory model.

The other possible cause of undefined behavior in the memory model are mixed-size accesses: Rustinherits the C++ limitation that non-synchronized conflicting atomic accesses may not partiallyoverlap. In other words, every pair of non-synchronized atomic accesses must be either disjoint,access the exact same memory (including using the same access size), or both be reads.

Each atomic access takes anOrdering which defines how the operation interacts with thehappens-before order. These orderings behave the same as the correspondingC++20 atomicorderings. For more information, see thenomicon.

usestd::sync::atomic::{AtomicU16, AtomicU8, Ordering};usestd::mem::transmute;usestd::thread;letatomic = AtomicU16::new(0);thread::scope(|s| {// This is UB: conflicting non-synchronized accesses, at least one of which is non-atomic.s.spawn(|| atomic.store(1, Ordering::Relaxed));// atomic stores.spawn(||unsafe{ atomic.as_ptr().write(2) });// non-atomic write});thread::scope(|s| {// This is fine: the accesses do not conflict (as none of them performs any modification).    // In C++ this would be disallowed since creating an `atomic_ref` precludes    // further non-atomic accesses, but Rust does not have that limitation.s.spawn(|| atomic.load(Ordering::Relaxed));// atomic loads.spawn(||unsafe{ atomic.as_ptr().read() });// non-atomic read});thread::scope(|s| {// This is fine: `join` synchronizes the code in a way such that the atomic    // store happens-before the non-atomic write.lethandle = s.spawn(|| atomic.store(1, Ordering::Relaxed));// atomic storehandle.join().expect("thread won't panic");// synchronizes.spawn(||unsafe{ atomic.as_ptr().write(2) });// non-atomic write});thread::scope(|s| {// This is UB: non-synchronized conflicting differently-sized atomic accesses.s.spawn(|| atomic.store(1, Ordering::Relaxed));    s.spawn(||unsafe{letdifferently_sized = transmute::<&AtomicU16,&AtomicU8>(&atomic);        differently_sized.store(2, Ordering::Relaxed);    });});thread::scope(|s| {// This is fine: `join` synchronizes the code in a way such that    // the 1-byte store happens-before the 2-byte store.lethandle = s.spawn(|| atomic.store(1, Ordering::Relaxed));    handle.join().expect("thread won't panic");    s.spawn(||unsafe{letdifferently_sized = transmute::<&AtomicU16,&AtomicU8>(&atomic);        differently_sized.store(2, Ordering::Relaxed);    });});

§Portability

All atomic types in this module are guaranteed to belock-free if they’reavailable. This means they don’t internally acquire a global mutex. Atomictypes and operations are not guaranteed to be wait-free. This means thatoperations likefetch_or may be implemented with a compare-and-swap loop.

Atomic operations may be implemented at the instruction layer withlarger-size atomics. For example some platforms use 4-byte atomicinstructions to implementAtomicI8. Note that this emulation should nothave an impact on correctness of code, it’s just something to be aware of.

The atomic types in this module might not be available on all platforms. Theatomic types here are all widely available, however, and can generally berelied upon existing. Some notable exceptions are:

  • PowerPC and MIPS platforms with 32-bit pointers do not haveAtomicU64 orAtomicI64 types.
  • Legacy ARM platforms like ARMv4T and ARMv5TE have very limited hardwaresupport for atomics. The bare-metal targets disable this moduleentirely, but the Linux targetsuse the kernel to assist (which comeswith a performance penalty). It’s not until ARMv6K onwards that ARM CPUshave support for load/store and Compare and Swap (CAS) atomics in hardware.
  • ARMv6-M and ARMv8-M baseline targets (thumbv6m-* andthumbv8m.base-*) only provideload andstore operations, and donot support Compare and Swap (CAS) operations, such asswap,fetch_add, etc. Full CAS support is available on ARMv7-M and ARMv8-MMainline (thumbv7m-*,thumbv7em* andthumbv8m.main-*).

Note that future platforms may be added that also do not have support forsome atomic operations. Maximally portable code will want to be carefulabout which atomic types are used.AtomicUsize andAtomicIsize aregenerally the most portable, but even then they’re not available everywhere.For reference, thestd library requiresAtomicBools and pointer-sized atomics, althoughcore does not.

The#[cfg(target_has_atomic)] attribute can be used to conditionallycompile based on the target’s supported bit widths. It is a key-valueoption set for each supported size, with values “8”, “16”, “32”, “64”,“128”, and “ptr” for pointer-sized atomics.

§Atomic accesses to read-only memory

In general,all atomic accesses on read-only memory are undefined behavior. For instance, attemptingto do acompare_exchange that will definitely fail (making it conceptually a read-onlyoperation) can still cause a segmentation fault if the underlying memory page is mapped read-only. Sinceatomicloads might be implemented using compare-exchange operations, even aload can faulton read-only memory.

For the purpose of this section, “read-only memory” is defined as memory that is read-only inthe underlying target, i.e., the pages are mapped with a read-only flag and any attempt to writewill cause a page fault. In particular, an&u128 reference that points to memory that isread-write mapped isnot considered to point to “read-only memory”. In Rust, almost all memoryis read-write; the only exceptions are memory created byconst items orstatic items withoutinterior mutability, and memory that was specifically marked as read-only by the operatingsystem via platform-specific APIs.

As an exception from the general rule stated above, “sufficiently small” atomic loads withOrdering::Relaxed are implemented in a way that works on read-only memory, and are hence notundefined behavior. The exact size limit for what makes a load “sufficiently small” variesdepending on the target:

target_archSize limit
x86,arm,loongarch32,mips,mips32r6,powerpc,riscv32,sparc,hexagon4 bytes
x86_64,aarch64,loongarch64,mips64,mips64r6,powerpc64,riscv64,sparc64,s390x8 bytes

Atomics loads that are larger than this limit as well as atomic loads with ordering otherthanRelaxed, as well asall atomic loads on targets not listed in the table, might still beread-only under certain conditions, but that is not a stable guarantee and should not be reliedupon.

If you need to do an acquire load on read-only memory, you can do a relaxed load followed by anacquire fence instead.

§Examples

A simple spinlock:

usestd::sync::Arc;usestd::sync::atomic::{AtomicUsize, Ordering};usestd::{hint, thread};fnmain() {letspinlock = Arc::new(AtomicUsize::new(1));letspinlock_clone = Arc::clone(&spinlock);letthread = thread::spawn(move|| {        spinlock_clone.store(0, Ordering::Release);    });// Wait for the other thread to release the lockwhilespinlock.load(Ordering::Acquire) !=0{        hint::spin_loop();    }if letErr(panic) = thread.join() {println!("Thread had an error: {panic:?}");    }}

Keep a global count of live threads:

usestd::sync::atomic::{AtomicUsize, Ordering};staticGLOBAL_THREAD_COUNT: AtomicUsize = AtomicUsize::new(0);// Note that Relaxed ordering doesn't synchronize anything// except the global thread counter itself.letold_thread_count = GLOBAL_THREAD_COUNT.fetch_add(1, Ordering::Relaxed);// Note that this number may not be true at the moment of printing// because some other thread may have changed static value already.println!("live threads: {}", old_thread_count +1);

Structs§

AtomicBooltarget_has_atomic_load_store=8
A boolean type which can be safely shared between threads.
AtomicI8
An integer type which can be safely shared between threads.
AtomicI16
An integer type which can be safely shared between threads.
AtomicI32
An integer type which can be safely shared between threads.
AtomicI64
An integer type which can be safely shared between threads.
AtomicIsize
An integer type which can be safely shared between threads.
AtomicPtrtarget_has_atomic_load_store=ptr
A raw pointer type which can be safely shared between threads.
AtomicU8
An integer type which can be safely shared between threads.
AtomicU16
An integer type which can be safely shared between threads.
AtomicU32
An integer type which can be safely shared between threads.
AtomicU64
An integer type which can be safely shared between threads.
AtomicUsize
An integer type which can be safely shared between threads.

Enums§

Ordering
Atomic memory orderings

Constants§

ATOMIC_BOOL_INITDeprecatedtarget_has_atomic_load_store=8
AnAtomicBool initialized tofalse.
ATOMIC_ISIZE_INITDeprecated64-bit
AnAtomicIsize initialized to0.
ATOMIC_USIZE_INITDeprecated64-bit
AnAtomicUsize initialized to0.

Traits§

AtomicPrimitiveExperimental
A marker trait for primitive types which can be modified atomically.

Functions§

compiler_fence
A “compiler-only” atomic fence.
fence
An atomic fence.
spin_loop_hintDeprecated
Signals the processor that it is inside a busy-wait spin-loop (“spin lock”).

Type Aliases§

AtomicExperimental
A memory location which can be safely modified from multiple threads.

[8]ページ先頭

©2009-2026 Movatter.jp