Skip to content

Commit f311e3d

Browse files
authored
Merge pull request #397 from async-rs/sync-docs
add mod level docs for sync
2 parents 526c4da + 3a06a12 commit f311e3d

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed

src/sync/mod.rs

+146
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,152 @@
44
//!
55
//! [`std::sync`]: https://doc.rust-lang.org/std/sync/index.html
66
//!
7+
//! ## The need for synchronization
8+
//!
9+
//! async-std's sync primitives are scheduler-aware, making it possible to
10+
//! `.await` their operations - for example the locking of a [`Mutex`].
11+
//!
12+
//! Conceptually, a Rust program is a series of operations which will
13+
//! be executed on a computer. The timeline of events happening in the
14+
//! program is consistent with the order of the operations in the code.
15+
//!
16+
//! Consider the following code, operating on some global static variables:
17+
//!
18+
//! ```rust
19+
//! static mut A: u32 = 0;
20+
//! static mut B: u32 = 0;
21+
//! static mut C: u32 = 0;
22+
//!
23+
//! fn main() {
24+
//! unsafe {
25+
//! A = 3;
26+
//! B = 4;
27+
//! A = A + B;
28+
//! C = B;
29+
//! println!("{} {} {}", A, B, C);
30+
//! C = A;
31+
//! }
32+
//! }
33+
//! ```
34+
//!
35+
//! It appears as if some variables stored in memory are changed, an addition
36+
//! is performed, result is stored in `A` and the variable `C` is
37+
//! modified twice.
38+
//!
39+
//! When only a single thread is involved, the results are as expected:
40+
//! the line `7 4 4` gets printed.
41+
//!
42+
//! As for what happens behind the scenes, when optimizations are enabled the
43+
//! final generated machine code might look very different from the code:
44+
//!
45+
//! - The first store to `C` might be moved before the store to `A` or `B`,
46+
//! _as if_ we had written `C = 4; A = 3; B = 4`.
47+
//!
48+
//! - Assignment of `A + B` to `A` might be removed, since the sum can be stored
49+
//! in a temporary location until it gets printed, with the global variable
50+
//! never getting updated.
51+
//!
52+
//! - The final result could be determined just by looking at the code
53+
//! at compile time, so [constant folding] might turn the whole
54+
//! block into a simple `println!("7 4 4")`.
55+
//!
56+
//! The compiler is allowed to perform any combination of these
57+
//! optimizations, as long as the final optimized code, when executed,
58+
//! produces the same results as the one without optimizations.
59+
//!
60+
//! Due to the [concurrency] involved in modern computers, assumptions
61+
//! about the program's execution order are often wrong. Access to
62+
//! global variables can lead to nondeterministic results, **even if**
63+
//! compiler optimizations are disabled, and it is **still possible**
64+
//! to introduce synchronization bugs.
65+
//!
66+
//! Note that thanks to Rust's safety guarantees, accessing global (static)
67+
//! variables requires `unsafe` code, assuming we don't use any of the
68+
//! synchronization primitives in this module.
69+
//!
70+
//! [constant folding]: https://en.wikipedia.org/wiki/Constant_folding
71+
//! [concurrency]: https://en.wikipedia.org/wiki/Concurrency_(computer_science)
72+
//!
73+
//! ## Out-of-order execution
74+
//!
75+
//! Instructions can execute in a different order from the one we define, due to
76+
//! various reasons:
77+
//!
78+
//! - The **compiler** reordering instructions: If the compiler can issue an
79+
//! instruction at an earlier point, it will try to do so. For example, it
80+
//! might hoist memory loads at the top of a code block, so that the CPU can
81+
//! start [prefetching] the values from memory.
82+
//!
83+
//! In single-threaded scenarios, this can cause issues when writing
84+
//! signal handlers or certain kinds of low-level code.
85+
//! Use [compiler fences] to prevent this reordering.
86+
//!
87+
//! - A **single processor** executing instructions [out-of-order]:
88+
//! Modern CPUs are capable of [superscalar] execution,
89+
//! i.e., multiple instructions might be executing at the same time,
90+
//! even though the machine code describes a sequential process.
91+
//!
92+
//! This kind of reordering is handled transparently by the CPU.
93+
//!
94+
//! - A **multiprocessor** system executing multiple hardware threads
95+
//! at the same time: In multi-threaded scenarios, you can use two
96+
//! kinds of primitives to deal with synchronization:
97+
//! - [memory fences] to ensure memory accesses are made visible to
98+
//! other CPUs in the right order.
99+
//! - [atomic operations] to ensure simultaneous access to the same
100+
//! memory location doesn't lead to undefined behavior.
101+
//!
102+
//! [prefetching]: https://en.wikipedia.org/wiki/Cache_prefetching
103+
//! [compiler fences]: https://doc.rust-lang.org/std/sync/atomic/fn.compiler_fence.html
104+
//! [out-of-order]: https://en.wikipedia.org/wiki/Out-of-order_execution
105+
//! [superscalar]: https://en.wikipedia.org/wiki/Superscalar_processor
106+
//! [memory fences]: https://doc.rust-lang.org/std/sync/atomic/fn.fence.html
107+
//! [atomic operations]: https://doc.rust-lang.org/std/sync/atomic/index.html
108+
//!
109+
//! ## Higher-level synchronization objects
110+
//!
111+
//! Most of the low-level synchronization primitives are quite error-prone and
112+
//! inconvenient to use, which is why async-std also exposes some
113+
//! higher-level synchronization objects.
114+
//!
115+
//! These abstractions can be built out of lower-level primitives.
116+
//! For efficiency, the sync objects in async-std are usually
117+
//! implemented with help from the scheduler, which is
118+
//! able to reschedule the tasks while they are blocked on acquiring
119+
//! a lock.
120+
//!
121+
//! The following is an overview of the available synchronization
122+
//! objects:
123+
//!
124+
//! - [`Arc`]: Atomically Reference-Counted pointer, which can be used
125+
//! in multithreaded environments to prolong the lifetime of some
126+
//! data until all the threads have finished using it.
127+
//!
128+
//! - [`Barrier`]: Ensures multiple threads will wait for each other
129+
//! to reach a point in the program, before continuing execution all
130+
//! together.
131+
//!
132+
//! - [`channel`]: Multi-producer, multi-consumer queues, used for
133+
//! message-based communication. Can provide a lightweight
134+
//! inter-task synchronisation mechanism, at the cost of some
135+
//! extra memory.
136+
//!
137+
//! - [`Mutex`]: Mutual Exclusion mechanism, which ensures that at
138+
//! most one task at a time is able to access some data.
139+
//!
140+
//! - [`RwLock`]: Provides a mutual exclusion mechanism which allows
141+
//! multiple readers at the same time, while allowing only one
142+
//! writer at a time. In some cases, this can be more efficient than
143+
//! a mutex.
144+
//!
145+
//! [`Arc`]: crate::sync::Arc
146+
//! [`Barrier`]: crate::sync::Barrier
147+
//! [`Condvar`]: crate::sync::Condvar
148+
//! [`channel`]: fn.channel.html
149+
//! [`Mutex`]: crate::sync::Mutex
150+
//! [`Once`]: crate::sync::Once
151+
//! [`RwLock`]: crate::sync::RwLock
152+
//!
7153
//! # Examples
8154
//!
9155
//! Spawn a task that updates an integer protected by a mutex:

0 commit comments

Comments
 (0)