How to do it...

  1. In the folder bin, create a file called atomic.rs.

  2. Add the following code and run it with cargo run --bin atomic:

1 use std::sync::Arc;
2 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering, ATOMIC_BOOL_INIT, ATOMIC_USIZE_INIT};
3 use std::thread;
4 use std::ops::{Deref, DerefMut};
5 use std::cell::UnsafeCell;
6
7 fn main() {
8 // Atomics are primitive types suited for
9 // well defined concurrent behaviour
10 let some_number = AtomicUsize::new(0);
11 // They are usually initialized by copying them from
12 // their global constants, so the following line does the same:
13 let some_number = ATOMIC_USIZE_INIT;
14
15 // load() gets the current value of the atomic
16 // Ordering tells the compiler how exactly to handle the
interactions
17 // with other threads. SeqCst ("Sequentially Consistent") can
always be used
18 // as it results in the same thing as if no parallelism was
involved
19 let curr_val = some_number.load(Ordering::SeqCst);
20 println!("The current value of some_number is {}", curr_val);
21
22 // store() sets the variable
23 some_number.store(123, Ordering::SeqCst);
24 let curr_val = some_number.load(Ordering::SeqCst);
25 println!("The current value of some_number is {}", curr_val);
26
27 // swap() sets the variable and returns the old value
28 let old_val = some_number.swap(12_345, Ordering::SeqCst);
29 let curr_val = some_number.load(Ordering::SeqCst);
30 println!("The old value of some_number was {}", old_val);
31 println!("The current value of some_number is {}", curr_val);
32
33 // compare_and_swap only swaps the variable if it
34 // is currently equal to the first argument.
35 // It will always return the old variable
36 let comparison = 12_345;
37 let new_val = 6_789;
38 let old_val = some_number.compare_and_swap(comparison, new_val,
Ordering::SeqCst);
39 if old_val == comparison {
40 println!("The value has been updated");
41 }
42
43 // The previous atomic code is equivalent to
44 // the following sequential code
45 let mut some_normal_number = 12_345;
46 let old_val = some_normal_number;
47 if old_val == comparison {
48 some_normal_number = new_val;
49 println!("The value has been updated sequentially");
50 }
51
52 // fetch_add() and fetch_sub() add/subtract a number from the
value,
53 // returning the old value
54 let old_val_one = some_number.fetch_add(12, Ordering::SeqCst);
55 let old_val_two = some_number.fetch_sub(24, Ordering::SeqCst);
56 let curr_val = some_number.load(Ordering::SeqCst);
57 println!(
58 "some_number was first {}, then {} and is now {}",
59 old_val_one, old_val_two, curr_val
60 );
61
62 // fetch_or() performs an "or" ("||") operation on the variable
and
63 // an argument and sets the variable to the result. It then
returns the old value.
64 // For the other logical operations, fetch_and(), fetch_nand()
and fetch_xor also exist
65 let some_bool = ATOMIC_BOOL_INIT;
66 let old_val = some_bool.fetch_or(true, Ordering::SeqCst);
67 let curr_val = some_bool.load(Ordering::SeqCst);
68 println!("({} || true) is {}", old_val, curr_val);
69
70 // The following is a demonstration of our own Mutex
implementation,
71 // based on an AtomicBool that checks if it's locked or not
72 let naive_mutex = Arc::new(NaiveMutex::new(1));
73
74 // The updater thread will set the value in the mutex to 2
75 let updater = {
76 let naive_mutex = naive_mutex.clone();
77 thread::spawn(move || {
78 let mut val = naive_mutex.lock();
79 *val = 2;
80 })
81 };
82
83 // The updater thread will print the value in the mutex
84 let printer = {
85 let naive_mutex = naive_mutex.clone();
86 thread::spawn(move || {
87 let val = naive_mutex.lock();
88 println!("The value in the naive mutex is: {}", *val);
89 })
90 };
91
92 // The exact order of execution is unpredictable,
93 // but our mutex guarantees that the two threads will
94 // never access the data at the same time
95 updater.join().expect("The updater thread panicked");
96 printer.join().expect("The printer thread panicked");
97 }
  1. Now comes the implementation of our very own homemade mutex:
99 // NaiveMutex is an easy, albeit very suboptimal,
100 // implementation of a Mutex, similar to std::sync::Mutex
101 // A mutex is a lock that only allows one thread to access a
ressource at all times
102 pub struct NaiveMutex<T> {
103 locked: AtomicBool,
104 // UnsafeCell is the underlying struct of every
105 // internally mutable container such as ours
106 data: UnsafeCell<T>,
107 }
108
109 // This is a RAII guard, identical to the one from the last
chapter
110 pub struct NaiveMutexGuard<'a, T: 'a> {
111 naive_mutex: &'a NaiveMutex<T>,
112 }
113
114 impl<T> NaiveMutex<T> {
115 pub fn new(data: T) -> Self {
116 NaiveMutex {
117 locked: ATOMIC_BOOL_INIT,
118 data: UnsafeCell::new(data),
119 }
120 }
121
122 pub fn lock(&self) -> NaiveMutexGuard<T> {
123 // The following algorithm is called a "spinlock", because it
keeps
124 // the current thread blocked by doing nothing (it keeps it
"spinning")
125 while self.locked.compare_and_swap(false, true,
Ordering::SeqCst) {}
126 NaiveMutexGuard { naive_mutex: self }
127 }
128 }
129
130 // Every type that is safe to send between threads is automatically
131 // safe to share between threads if wrapped in our mutex, as it
132 // guarantees that no threads will access it ressource at the
same time
133 unsafe impl<T: Send> Sync for NaiveMutex<T> {}
134
135 // Automatically unlock the mutex on drop
136 impl<'a, T> Drop for NaiveMutexGuard<'a, T> {
137 fn drop(&mut self) {
138 self.naive_mutex.locked.store(false, Ordering::SeqCst);
139 }
140 }
141
142 // Automatically dereference to the underlying data
143 impl<'a, T> Deref for NaiveMutexGuard<'a, T> {
144 type Target = T;
145 fn deref(&self) -> &T {
146 unsafe { &*self.naive_mutex.data.get() }
147 }
148 }
149
150 impl<'a, T> DerefMut for NaiveMutexGuard<'a, T> {
151 fn deref_mut(&mut self) -> &mut T {
152 unsafe { &mut *self.naive_mutex.data.get() }
153 }
154 }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.210.91