Atomic (thread-safe) counters for Rust.
This crate contains an AtomicCounter
trait
that can safely be shared across threads.
This crate provides two implementations:
-
RelaxedCounter
which is suitable for e.g. collecting metrics or generate IDs, but which does not provide "Sequential Consistency".RelaxedCounter
usesRelaxed
memory ordering. -
ConsistentCounter
which provides the same interface but is sequentially consistent. Use this counter if the order of update from multiple threads is important.ConsistentCounter
usesSequentially Consistent
memory ordering.
Both implementations are lock-free. Both are a very thin layer over
AtomicUsize
which is more powerful but might be harder to use correctly.
-
If you are just collecting metrics, the
RelaxedCounter
is probably right choice. -
If you are generating IDs, but don't make strong assumptions (like allocating memory based on the ID count),
RelaxedCounter
is probably the right choice. -
If you are generating multiple IDs where you maintain an ordering invariant (e.g. ID
a
is always greater than IDb
), you need "Sequential Consistency" and thus need to useConsistentCounter
. The same is true for all use cases where the ordering of incrementing the counter is important.
Note that in both implementations, no count is lost and all operations are atomic. The difference is only in how the order of operations are observed by different threads.
Assume a
is 5 and b
is 4. You always want to maintain a > b
.
Thread 1 executes this code:
a.inc();
b.inc();
Thread 2 gets counts:
let a_local = a.get();
let b_local = b.get();
What are the values for a_local
and b_local
? That depends on the order
in which thread 1 and 2 have run:
a_local
could still be 5 andb_local
is still be 4 (e.g. if thread 2 ran before thread 1)a_local
could be increment to 6 whileb_local
is still at 4 (e.g. if thread 1 and 2 ran in parallel)a_local
could be increment to 6 andb_local
be incremented to 5 (e.g. if thread 2 ran after thread 1).- Additionally, if at least one counter is a
RelaxedCounter
, we cannot make assumption on the order ofa.inc()
andb.inc()
. Thus, in this case thread 2 can also observea_local
to be 5 (not incremented yet) butb_local
to be incremented to 5, breaking the invarianta > b
. Note that if thread 2 (or any other thread)get()
the counts again, at some point they will observe both values to be incremented. No operations will be lost. It is only the ordering of the operations that cannot be assumed ifOrdering
isRelaxed
.
So in order to maintain invariants such as a > b
across multiple threads,
use ConsistentCounter
.