You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: concurrency-primer.tex
+64-4Lines changed: 64 additions & 4 deletions
Original file line number
Diff line number
Diff line change
@@ -747,6 +747,66 @@ \section{Do we always need sequentially consistent operations?}
747
747
748
748
\section{Memory orderings}
749
749
750
+
\subsection{Memory consistency models}
751
+
752
+
When a program is compiled and executed, it doesn't always follow the written order. The system may change the sequence and optimize it to simulate line-by-line execution, as long as the final result matches the expected outcome.
753
+
754
+
This requires an agreement between the programmer and the system (hardware, compiler, etc.), ensuring that if the rules are followed, the execution will be correct. Correctness here means defining permissible outcomes among all possible results, known as Memory Consistency Models. These models allow the system to optimize while ensuring correct execution.
755
+
756
+
Memory Consistency Models operate at various levels. For example, when machine code runs on hardware, processors can reorder and optimize instructions, and the results must match expectations. Similarly, when converting high-level languages to assembly, compilers can rearrange instructions while ensuring consistent outcomes. Thus, from source code to hardware execution, agreements must ensure the expected results.
757
+
758
+
One can envision hardware that achieves sequential consistency as follows: each thread has direct access to shared memory, and memory processes only one read or write operation at a time. This naturally ensures sequential consistency.
759
+
760
+
\subsubsection{Sequential consistency (SC)}
761
+
762
+
In the 1970s, Leslie Lamport proposed the most common memory consistency model, Sequential Consistency (SC), defined as follows:
763
+
764
+
\begin{quote}
765
+
A multiprocessor system is sequentially consistent if the result of any execution is the same as if the operations of all the processors were executed in some sequential order, and the operations of each individual processor appear in this sequence in the order specified by its program.
766
+
\end{quote}
767
+
768
+
On modern processors, ensuring sequential consistency involves many optimization constraints, which slow down program execution. If some conventions are relaxed, such as not guaranteeing program order within each processing unit, performance can be further improved.
769
+
770
+
A memory consistency model is a conceptual convention. This means the program's execution results must conform to this model. However, when a program is compiled and run on computer hardware, there is significant flexibility in adjusting the execution order. As long as the execution results match the predefined convention, the actual order can vary depending on the circumstances.
771
+
772
+
It is important to note that sequential consistency does not imply a single order or a single result for the program. On the contrary, sequential consistency only requires that the program appears to execute in some interleaved order on a single thread, meaning a sequentially consistent program can still have multiple possible results.
773
+
774
+
To enhance the understanding of sequential consistency, consider the following simple example. Two threads write to and read from two shared variables \monobox{x} and \monobox{y}, both initially set to \monobox{0}.
775
+
776
+
\begin{ccode}
777
+
int x = 0;
778
+
int y = 0;
779
+
780
+
// Thread 1 // Thread 2
781
+
x = 1; r1 = y;
782
+
y = 1; r2 = x;
783
+
\end{ccode}
784
+
785
+
If this program satisfies sequential consistency, then for Thread 1, \monobox{x = 1} must occur before \monobox{y = 1}, and for Thread 2, \monobox{r1 = y} must occur before \monobox{r2 = x}. For the entire program, the following six execution orders are possible:
Observing these orders, we see that none result in \monobox{r1 = 1} and \monobox{r2 = 0}. Thus, sequential consistency only allows the outcomes \monobox{(r1, r2)} to be \monobox{(1, 1)}, \monobox{(0, 1)}, and \monobox{(0, 0)}. With this convention, software can expect that \monobox{(1, 0)} will not occur, and hardware can optimize as long as it ensures the result \monobox{(1, 0)} does not appear.
800
+
801
+
We can imagine sequentially consistent hardware as the figure \ref{hw-seq-cst} shows: each thread can directly access shared memory, and memory processes one read or write operation at a time, naturally ensuring sequential consistency. In fact, there are multiple ways to implement sequentially consistent hardware. It can even include caches and be banked, as long as it ensures that the results behave the same as the aforementioned model.
\captionof{figure}{The memory model of sequentially consistent hardware.}
806
+
\label{hw-seq-cst}
807
+
808
+
\subsection{C11/C++11 atomics}
809
+
750
810
By default, all atomic operations, including loads, stores, and various forms of \textsc{RMW},
751
811
are considered sequentially consistent.
752
812
However, this is just one among many possible orderings.
@@ -793,7 +853,7 @@ \section{Memory orderings}
793
853
let's look at what these orderings are and how we can use them.
794
854
As it turns out, almost all of the examples we have seen so far do not actually need sequentially consistent operations.
795
855
796
-
\subsection{Acquire and release}
856
+
\subsubsection{Acquire and release}
797
857
798
858
We have just examined the acquire and release operations in the context of the lock example from \secref{lock-example}.
799
859
You can think of them as ``one-way'' barriers: an acquire operation permits other reads and writes to move past it,
@@ -858,7 +918,7 @@ \subsection{Acquire and release}
858
918
}
859
919
\end{cppcode}
860
920
861
-
\subsection{Relaxed}
921
+
\subsubsection{Relaxed}
862
922
Relaxed atomic operations are useful for variables shared between threads where \emph{no specific order} of operations is needed.
863
923
Although it may seem like a niche requirement, such scenarios are quite common.
864
924
@@ -908,7 +968,7 @@ \subsection{Relaxed}
908
968
All of the loads can be relaxed as we do not need to enforce any order until we have successfully modified our value.
909
969
The initial load of \texttt{expected} is not strictly necessary but can help avoid an extra loop iteration if \texttt{foo} remains unmodified by other threads before the \textsc{CAS} operation.
910
970
911
-
\subsection{Acquire-Release}
971
+
\subsubsection{Acquire-Release}
912
972
913
973
\cc|memory_order_acq_rel| is used with atomic \textsc{RMW} operations that need to both load-acquire \emph{and} store-release a value.
914
974
A typical example involves thread-safe reference counting,
@@ -958,7 +1018,7 @@ \subsection{Acquire-Release}
958
1018
experts-only construct we have in the language.
959
1019
\end{quote}
960
1020
961
-
\subsection{Consume}
1021
+
\subsubsection{Consume}
962
1022
963
1023
Last but not least, we introduce \cc|memory_order_consume|.
964
1024
Imagine a situation where data changes rarely but is frequently read by many threads.
0 commit comments