ˠ˱́˱˼˼˶˼̍˾̌˶˳̌̈˹̂˼˹̃˶˼̍˾̌˶̃˶̆˾˿˼˿˴˹˹ 
˟̂˶˾̍3DUDOOHORPSXWLQJ7HFKQRORJLHV37
˜˶˻̇˹̐ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ 
˓˾˶˿̈˶́˶˵˾˿˶˳̌̀˿˼˾˶˾˹˶˹˾̂̃́̄˻̇˹˺ 
˒˱́̍˶́̌̀˱˽̐̃˹ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱ 
˿̂˳˿˲˿˷˵˶˾˹̐˝˿˵˶˼̍̀˱˽̐̃˹ 
ˠ˱˸˾˹˻˿˳ˑ˼˶˻̂˶˺ˑ˼˶˻̂˱˾˵́˿˳˹̈ 
˛˱̅˶˵́˱˳̌̈˹̂˼˹̃˶˼̍˾̌̆̂˹̂̃˶˽ˢ˹˲˔ˤˣ˙ 
ˢ˱˺̃˻̄́̂˱KWWSFSFWVLEVXWLVUXaDSD]QLNRYWHDFKLQJ 
˓˿̀́˿̂̌KWWSVSLD]]DFRPVLEVXWLVUXIDOOSFWKRPH
ˑ̃˿˽˱́˾̌˶ 
̀˶́˶˽˶˾˾̌˶ 
2
ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ 
▪ Операция над разделяемой переменной 
атомарная, если она выполняется потоком 
за один неделимый шаг. Ни один из других 
потоков не может обнаружить эту переменную 
в промежуточном состоянии. 
▪ Если операции, которые совершают потоки над 
раздялемыми переменными, не атомарны, то это 
приведёт к гонкам данных. 
▪ Гонки данных являются причиной неопредлённого 
поведения, поскольку они приводят к частичным 
(фрагментированным, “разорванным”) 
чтениям и записям переменных. 
3
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
запись младших 32 бит 
запись старших 32 бит 
▪ Выполнение присваивания 64-битного целого shared = 42 
на 32-разрядной архитектуре выполняется 
за 2 инструкции. 
▪ Операция записи не атомарная. 
4
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
1 поток 
запись старших 32 бит 
▪ Вытеснение потока после записи младших бит приведёт к 
тому, что эти биты останутся в памяти и будут 
использованы другими потоками. 
▪ На многоядерных системах даже не требуется 
вытеснения. 5
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
int main() { 
shared = 0x100000002; 
... 
gcc -m32 -S -masm=intel -O1 prog.c 
mov DWORD PTR shared, 2 
mov DWORD PTR shared+4, 1 
1 поток 
2 поток 
▪ Вытеснение потока после записи младших бит приведёт к 
тому, что эти биты останутся в памяти, а старшие будут 
записаны другим потоком. 
▪ На многоядерных системах даже не требуется 
вытеснения. 6
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
uint64_t shared; 
uint64_t getShared() { 
return shared; 
} 
gcc -m32 -S -masm=intel -O1 prog.c 
mov eax, DWORD PTR shared 
mov edx, DWORD PTR shared+4 
ret 
чтение младших 32 бит 
чтение старших 32 бит 
▪ Выполнение чтения 64-битного целого shared на 32- 
разрядной архитектуре выполняется 
за 2 инструкции. 
▪ Операция чтения не атомарная. 
7
˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ 
▪ Инструкции могут быть неатомарными, даже если 
выполняются одной процессорной инструкцией. 
Например, в ARMv7 инструкция для помещения содержимого 
двух 32-битных регистров в один 64-битный: 
strd r0, r1, [r2] 
На некоторых процессорах эта инструкция реализуются двумя 
отдельными операциями сохранения. 
32-битная операция mov атомарна только для выравненных 
данных. В остальных случаях операция неатомарная 
▪ Вытеснение потоков, выполняющих данные операции, или 
выполнение операций в двугих потоках в многоядерных 
системах приводит к неопределённому поведению. 
8
ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ 
▪ В языках С и С++ предполагается, что все 
операции неатомарны. 
▪ Операции могут быть атомарными в 
большинстве случаев, например, операция 
присваивания 32-битного целого значения. 
▪ Тем не менее, все инструкции должны 
рассматриваться как неатомарные. 
▪ К счастью, в С и С++ есть набор шаблонов 
атомарных типов данных. 
9
ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ 
▪ Атомарные операции в С++ неделимы. Из 
любого потока нельзя обнаружить эту 
операцию выполненной частично - она 
либо выполнена, либо невыполнена. 
▪ Это позволяет избежать гонок данных. 
▪ Для атомарных типов определён метод 
is_lock_free, позволяющий определить, 
являются ли операции над ним напрямую 
с помощью атомарных инструкций, или они 
эмулируются. 
10
ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ 
▪ std::atomic_flag - единственный тип, 
который не имеет функции is_lock_free. Он 
предельно простой, всецело атомарный и 
поддерживает одну операцию: test_and_set - 
проверить и установить. 
▪ Остальные типы определяются 
специализацией шаблона std::atomic, 
например std::atomicint и std:: 
atomicvoid* 
11
ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ 
Атомарные тип Соответствующая специализация 
std::atomic_bool std::atomicbool 
std::atomic_char std::atomicchar 
std::atomic_schar std::atomicsigned char 
std::atomic_uchar std::atomicunsigned char 
std::atomic_short std::atomicshort 
std::atomic_ushort std::atomicunsigned short 
std::atomic_int std::atomicint 
std::atomic_uint std::atomicunsigned int 
std::atomic_long std::atomiclong 
... 
+ пользовательские типы 
12
˟̀˶́˱̇˹˹˾˱˵˱̃˿˽˱́˾̌˽˹̃˹̀˱˽˹ 
▪ Операции сохранения: store, clear, etc. 
Упорядочение memory_order_relaxed, 
memory_order_release, memory_order_seq_cst. 
▪ Операции загрузки: load, etc. 
Упорядочение memory_order_relaxed, 
memory_order_consume, memory_order_acquire, 
memory_order_seq_cst. 
▪ Операции чтения-модификации-записи: 
compare_exchange, fetch_add, test_and_set, etc. 
Упорядочение memory_order_relaxed, 
memory_order_consume, memory_order_acquire, 
memory_order_release, memory_order_seq_cst, 
memory_order_acq_rel. 
13
ˑ̃˿˽˱́˾̌˺̅˼˱˴VWGDWRPLFBIODJ 
std::atomic_flag должен быть проиниализирован: 
std::atomic_flag flag = ATOMIC_FLAG_INIT 
Очистить флаг (операция сохранения): установить значение 
false: 
flag.clear(std::memory_order_release); 
Установить значение флага в true и вернуть предыдущее 
значение: 
bool x = flag.test_and_set(); 
Для атомарного флага запрещены операции копирования и 
присваивания. 
14
ˡ˶˱˼˹˸˱̇˹̐̂̀˹˾˼˿˻˱˾˱˿̂˾˿˳˶˱̃˿˽˱́˾˿˴˿̅˼˱˴˱ 
class spinlock_mutex 
{ 
std::atomic_flag flag; 
public: 
spinlock_mutex(): 
flag{ATOMIC_FLAG_INIT} { } 
void lock() { 
while (flag.test_and_set( 
std::memory_order_acquire)); 
} 
void unlock() { 
flag.clear(std::memory_order_release); 
} 
}; 
n.b. Можно использовать с lock_guard и unique_guard! 15
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸˱˴́̄˸˻˹˱̃˿˽˱́˾̌̆̃˹̀˿˳ 
// Объявить переменную и проициализировать true 
std::atomicbool b(true); 
// Загрузить значение в переменной в неатомарную 
// переменную x 
bool x = b.load(std::memory_order_acquire); 
// Записать в переменную b значение true 
b.store(true); 
// Обменять значение переменной b со значением false, 
// вернуть предыдущее значение b в переменную x. 
x = b.exchange(false, std::memory_order_acq_rel); 
16
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
Операция compare_exchange (“сравнить и обменять”): 
1. Сравнить текущее значение атомарной переменной с 
ожидаемым expected. 
2. Если значения совпали, сохранить новое значение 
desired и вернуть true. 
3. Если значения не совпадают, то ожидаемое значение 
expected заменяется фактическим значением 
переменной, функция возвращает false. 
bool compare_exchange_weak(T expected, T desired, 
std::memory_order order = 
std::memory_order_seq_cst); 
bool compare_exchange_strong(T expected, T desired, 
std::memory_order order = 
std::memory_order_seq_cst); 
17
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
compare_exchange_weak() - сохранение может не 
произойти, даже если текущее значение совпадает с 
ожидаемым. Значение переменной не изменится, функция 
возвращает false. 
Последнее возможно при отсутствии аппаратной 
поддержки команды сравнить-и-обменять, из-за того, что 
поток может быть вытеснен в середине требуемой 
последовательности команд (ложный отказ). 
Из-за возможного ложного отказа функцию 
compare_exchange_weak() обычно вызывают в цикле: 
bool expected = false; 
extern atomicbool b; 
... 
while (!b.compare_exchange_weak(expected, true); 
18
˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r 
compare_exchange_strong() гарантирует замену 
переменной в случае выполнения условия. 
▪ compare_exchange_strong() выгодно использовать в 
случае однократном выполнении операции, т.е. при 
необходимости заменить значение на жалаемое, и в 
случае, если вычисление нового значения занимает 
длительное время. 
▪ Если функция compare_exchange вызывается в цикле, 
тогда предпочтительнее использовать 
compare_exchange_weak, чтобы избежать двойного 
цикла (compare_exchange_strong реализован в виде 
цикла на системах, которые не поддерживают 
атомарной операции сравнения и замены). 
19
ˑ̃˿˽˱́˾̌˺̃˹̀DWRPLF7
! 
Функции для типа atomicT*: 
▪ is_lock_free, load, store, exchange, 
compare_exchange_weak, compare_exchange_strong 
▪ обменять и прибавить: 
fetch_add, operator++, operator+= 
▪ обменять и вычесть: 
fetch_sub, operator--, operator-= 
class C {}; 
C arr[10]; 
std::atomicC* ptr(arr); 
C* x = ptr.fetch_add(2); 
assert(x == arr); 
assert(p.load() == some_array[2]); 
p--; x = p; 
assert(x == arr[1]); 
операции 
чтения- 
модификации- 
записи 
20
ˢ̃˱˾˵˱́̃˾̌˶˱̃˿˽˱́˾̌˶̇˶˼˿̈˹̂˼˶˾˾̌˶̃˹̀̌ 
Функции для типов стандартных атомарных 
целочисленных типов (int, unsigned int, long, 
unsigned long, etc): 
▪ is_lock_free, load, store, exchange, 
compare_exchange_weak, 
compare_exchange_strong 
▪ обменять и прибавить (fetch_add, operator++, 
operator+=) обменять и вычесть (fetch_sub, 
operator--, operator-=) 
▪ operator=, operator|=, operator^= 
Отсутствуют операции: 
▫ умножения, деления, сдвига 
21
ˠ˿˼̍˸˿˳˱̃˶˼̍̂˻˹˶˱̃˿˽˱́˾̌˶̃˹̀̌ 
В качестве шаблона std::atomic может 
выступать тип, он должен удовлетворять 
требованиям 
▪ В нём должен присутствовать тривиальный 
оператор присваивания. Нет виртуальных 
функций и виртуальных базовых классов, а 
оператор присваивания генерируется 
автоматически (например, memcpy) 
▪ Тип должен допускать побитовое сравнение на 
равенство (например, с помощью memcmp) 
22
˞˶̂˻˿˼̍˻˿ 
̀́˹˽˶́˿˳̀˿̂˼˶˵̂̃˳˹˺ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐ 
˳ˢ 
23
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
x = y = v = w = 0 
thread1() { 
x = 1 
v = y 
} 
thread2() { 
y = 1 
w = x 
} 
main() { 
start_threads(thread1, thread2) 
join_all_threads() 
assert(v != 0 || w != 0) assert может сработать! 
24
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
ready = false 
producer() { 
data = 42 
ready = true 
} 
consumer() { 
while (!ready) 
{ } 
print(data) 
} 
data = 42 не гарантируется 
25
ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ 
x = y = false, z = 0 
thread_store_x() { x = true } 
thread_store_y() { y = true } 
thread_read_x_then_y() { 
while (!x) { } 
if (y) z++ 
} 
thread_read_y_then_x() { 
while (!y) { } 
if (x) z++ 
} 
main { 
run_all_threads(store_x, store_y, 
read_x_then_y, read_y_then_x) 
join_all_threads() 
assert(z != 0) assert может сработать! 26
˝˶̃˱˽˿́̅˿˸̌ 
̀́˿˴́˱˽˽̌ 
˹˼˹̂̃́˱̉˾˱̐̀́˱˳˵˱ 
27
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
28
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
НЕТ 
29
˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ 
Выполняет ли компьютер программу, которую вы 
написали? 
НЕТ Просто дело в том что... 
иерархическая структура памяти, внеочередное 
выполнение команд процессора и компиляторная 
оптимизация. 30
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Процессорные 
ядра 
31
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Когерентность 
кэша (MESI, 
MOESI, MESIF) 
Процессорные 
ядра 
32
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Когерентность 
кэша (MESI, 
MOESI, MESIF) 
Внеочередное 
выполнение 
инструкций (out-of- 
order execution) 
Процессорные 
ядра 
33
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
Intel 64 CISC macro-instructions 
Instruction Fetch  
PreDecode 
Instruction Queue (IQ) 
Decode 
Rename/Allocate 
Retirement Unit 
(Re-Order 
Buffer) 
Scheduler 
Reservation 
Stations 
Execution Units 
Intel 64 CISC 
macro-instr. 
Execution 
Engine 
(out-of-order) 
ITLB Instruction Cache (32KiB) 
L2 TLB 
L2 Cache 
(256 KiB, 8-way) 
DTLB Data Cache (32KiB) 
L3 
Cache 
Front-End 
Pipeline 
(in-order) 
Nehalem RISC 
micro-operations 
,QWHO1HKDOHPRUH 
3LSHOLQH 
34
ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
16 
ITLB L1 I-cache (32 KiB, 4-way) 
byte/cycle 
Instruction 
Fetch Unit 
(IFU) 
Pre Decode, 
Prefetch Buffer, 
Instruction Length 
Decoder 
Instruction Queue (IQ) 
(18 entry – 18 instruction max.) 
Instruction Decoding Unit (IDU) 
3 simple + 1 complex 
Simple Complex 
Simple Simple micro-cod 
Decoded Instruction Queue (DIQ, 28 uops. max) 
Loop Stream Detection, Micro-Fusion, Macro- 
Fusion 
Intel64 CISC 
macro-instr. 
Nehalem 
RISC 
micro-operations 
4 micro-ops. 
/cycle 
Unified L2- 
Cache 
6 instr./cycle 
Branch 
Prediction 
Unit (BPU) 
5 instructions/cycle 
4 uops./cycle 
35
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
▪ Peephole-оптимизация 
▪ Локальная оптимизация 
▪ Внутрипроцедурная оптимизация 
▪ Оптимизация циклов 
▪ Межпроцедурная оптимизация 
36
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
▪ Peephole-оптимизация 
▪ Локальная оптимизация 
▪ Внутрипроцедурная оптимизация 
▪ Оптимизация циклов 
▫ Анализ индуктивных переменных 
▫ Деление цикла на части 
▫ Объединение циклов 
▫ Инверсия цикла 
▫ Расщепление цикла 
▪ Межпроцедурная оптимизация 37
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ 
-O 
-fauto-inc-dec 
-fbranch-count-reg 
-fcombine-stack-adjustments 
-fcompare-elim 
-fcprop-registers 
-fdce 
-fdefer-pop 
-fdelayed-branch 
-fdse 
-fforward-propagate 
-fguess-branch-probability 
-fif-conversion2 
-fif-conversion 
-finline-functions-called-once 
-fipa-pure-const 
-fipa-profile 
-fipa-reference 
-fmerge-constants 
-fmove-loop-invariants 
-fshrink-wrap 
-fsplit-wide-types 
-ftree-bit-ccp 
-ftree-ccp 
-fssa-phiopt 
-ftree-ch 
-ftree-copy-prop 
-ftree-copyrename 
-ftree-dce 
-ftree-dominator-opts 
-ftree-dse 
-ftree-forwprop 
-ftree-fre 
-ftree-phiprop 
-ftree-sink 
-ftree-slsr 
-ftree-sra 
-ftree-pta 
-ftree-ter 
-funit-at-a-time 
-O2 
-fauto-inc-dec 
-fbranch-count-reg 
-fcombine-stack-adjustments 
-fcompare-elim 
-fcprop-registers 
-fdce 
-fdefer-pop 
-fdelayed-branch 
-fdse 
-fforward-propagate 
-fguess-branch-probability 
-fif-conversion2 
-fif-conversion 
-finline-functions-called-once 
-fipa-pure-const 
-fipa-profile 
-fipa-reference 
-fmerge-constants 
-fmove-loop-invariants 
-fshrink-wrap 
-fsplit-wide-types 
-ftree-bit-ccp 
-ftree-ccp 
-fssa-phiopt 
-ftree-ch 
-ftree-copy-prop 
-ftree-copyrename 
-ftree-dce 
-ftree-dominator-opts 
-ftree-dse 
-ftree-forwprop 
-ftree-fre 
-ftree-phiprop 
-ftree-sink 
-ftree-slsr 
-ftree-sra 
-ftree-pta 
-ftree-ter 
-funit-at-a-time 
-fthread-jumps 
-falign-functions -falign-jumps 
-falign-loops 
-falign-labels 
-fcaller-saves 
-fcrossjumping 
-fcse-follow-jumps -fcse-skip-blocks 
-fdelete-null-pointer-checks 
-fdevirtualize -fdevirtualize-speculatively 
-fexpensive-optimizations 
-fgcse 
-fgcse-lm 
-fhoist-adjacent-loads 
-finline-small-functions 
-findirect-inlining 
-fipa-cp 
-fipa-sra 
-fipa-icf 
-fisolate-erroneous-paths-dereference 
-foptimize-sibling-calls 
-foptimize-strlen 
-fpartial-inlining 
-fpeephole2 
-freorder-blocks -freorder-blocks- 
and-partition -freorder-functions 
-frerun-cse-after-loop 
-fsched-interblock -fsched-spec 
-fschedule-insns -fschedule-insns2 
-fstrict-aliasing -fstrict-overflow 
-ftree-builtin-call-dce 
-ftree-switch-conversion - 
ftree-tail-merge 
-ftree-pre 
-ftree-vrp 
-fuse-caller-save 
-O3 
-fauto-inc-dec 
-fbranch-count-reg 
-fcombine-stack-adjustments 
-fcompare-elim 
-fcprop-registers 
-fdce 
-fdefer-pop 
-fdelayed-branch 
-fdse 
-fforward-propagate 
-fguess-branch-probability 
-fif-conversion2 
-fif-conversion 
-finline-functions-called-once 
-fipa-pure-const 
-fipa-profile 
-fipa-reference 
-fmerge-constants 
-fmove-loop-invariants 
-fshrink-wrap 
-fsplit-wide-types 
-ftree-bit-ccp 
-ftree-ccp 
-fssa-phiopt 
-ftree-ch 
-ftree-copy-prop 
-ftree-copyrename 
-ftree-dce 
-ftree-dominator-opts 
-ftree-dse 
-ftree-forwprop 
-ftree-fre 
-ftree-phiprop 
-ftree-sink 
-ftree-slsr 
-ftree-sra 
-ftree-pta 
-ftree-ter 
-funit-at-a-time 
-fthread-jumps 
-falign-functions -falign-jumps 
-falign-loops 
-falign-labels 
-fcaller-saves 
-fcrossjumping 
-fcse-follow-jumps -fcse-skip-blocks 
-fdelete-null-pointer-checks 
-fdevirtualize -fdevirtualize-speculatively 
-fexpensive-optimizations 
-fgcse 
-fgcse-lm 
-fhoist-adjacent-loads 
-finline-small-functions 
-findirect-inlining 
-fipa-cp 
-fipa-sra 
-fipa-icf 
-fisolate-erroneous-paths-dereference 
-foptimize-sibling-calls 
-foptimize-strlen 
-fpartial-inlining 
-fpeephole2 
-freorder-blocks -freorder-blocks-and- 
partition -freorder-functions 
-frerun-cse-after-loop 
-fsched-interblock -fsched-spec 
-fschedule-insns -fschedule-insns2 
-fstrict-aliasing -fstrict-overflow 
-ftree-builtin-call-dce 
-ftree-switch-conversion -ftree-tail- 
merge 
-ftree-pre 
-ftree-vrp 
-fuse-caller-save 
-finline-functions 
-funswitch-loops 
-fpredictive-commoning 
-fgcse-after-reload 
-ftree-loop-vectorize 
-ftree-loop-distribute-patterns 
-ftree-slp-vectorize 
-fvect-cost-model 
-ftree-partial-pre 
-fipa-cp-clone 
38
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
x = 1; 
y = 2; 
x = 3; 
for (i = 0; i  n; i++) 
sum += a[i]; 
y = 2; 
x = 3; 
r1 = sum; 
for (i = 0; i  n; i++) 
r1 += a[i]; 
sum = r1; 
for (i = 0; i  n; i++) 
j = 42 * i; 
j = -42 
for (i = 0; i  n; i++) 
j = j + 42; 
39
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
x = Johann; 
y = Sebastian; 
z = Bach; 
for (i = 0; i  n; i++) 
for (j = 0; j  m;j++); 
a[i][j] = 1; 
z = Bach; 
x = Johann; 
y = Sebastian; 
for (j = 0; j  n; j++) 
for (i = 0; i  m; 
i++); 
a[i][j] = 1; 
for (i = 0; i  n; i++) 
a[i] = 1; 
for (i = 0; i  n; i++) 
b[i] = 2; 
for (i = 0; i  n; i++) 
{ 
a[i] = 1; 
b[i] = 2; 
} 40
˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ 
Что компилятор знает: 
Все операции с памятью совершаются в текущем 
потоке, что они в точности означают, и какие 
существуют зависимости по данным. 
Что компилятор не знает: 
Какие области памяти доступны и изменяются в 
разных потоках. 
Как решить: 
Сказать! Как-то пометить операции, которые 
выполняются с разделяемыми переменными. 
41
ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
Исходный код 
Компилятор 
удаление подвыражений, свёртка 
констант, оптимизация циклов, ... 
Процессор 
предвыборка, спекулятивное 
выполнение инструкций, 
буферизация, HTM, ... 
Кэш 
частные и общие кэши, буферы 
записи, ... 
Реальное выполнение 
программы 
“Гораздо лучше 
выполнять другую 
программу - не ту, что 
вы написали. Вам на 
самом деле даже не 
хочется выполнять эту 
вашу чушь - вы хотите 
запускать другую 
программу! Всё это 
делается для вашего 
блага.” 
42
ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 
▪ Как правило нельзя 
определить, на каком уровне 
произошла трансформация. 
▪ Трансформации на всех 
уровнях эквивалентны, что 
позволяет рассматривать их 
как переупорядочивание 
операций загрузки (loads) 
и записи (stores). 
▪ Необходимое условие при 
выполнении трансформаций 
- сохранение иллюзии 
последовательно 
согласованного кода. 
43 
Исходный код 
Компилятор 
удаление подвыражений, свёртка 
констант, оптимизация циклов, ... 
Процессор 
предвыборка, спекулятивное 
выполнение инструкций, 
буферизация, HTM, ... 
Кэш 
частные и общие кэши, буферы 
записи, ... 
Реальное выполнение 
программы
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
44
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
И это прекрасно! 
45
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
“Результат выполнения программы такой, как если бы 
операции всех процессоров выполнялись последовательно и 
результат операции каждого отдельного процессора 
появлялся бы в этой последовательности в порядке, 
который определяется программой.” (Л. Лэмпорт, 1979) 
▪ Рассмотрим многопроцессорную систему, состоящую из 
нескольких последовательных процессоров. 
▪ Операции, выполняемые процессорами над некоторой 
областью памяти (страница, объект, адрес, ...), 
появляются в одном и том же порядке для всех 
процессоров, несмотря на то, что фактическая 
последовательность выполнения операций может 
быть другой. 
И это прекрасно! 
но...46
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF6
… но вы этого не хотите! 
▪ Скорее всего очень нерационально выполнять в точности 
то, что вы написали. 
▪ Гораздо лучше выполнить нечто иное, которое бы работало 
так же, как и то, что вы написали, но выполнялось бы 
гораздо быстрее. 
Поэтому 
▪ Мы (программное обеспечение ПО: компилятор и 
аппаратное обеспечение АО: кэш, процессор) будем это 
делать! 
▪ А вы (программисты), в свою очередь, должны обеспечить 
возможность корректной трансформации и выполнения 
программы так, чтобы сохранялась иллюзия 
последовательной согласованности, включив в свою 
программу необходимые ограничения. 47
˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ 
Вы обещаете 
Корректно 
реализовать 
синхронизацию 
в вашей 
программе 
(путём добавления 
необходимых 
инструкций в 
программу, 
делающих её 
безопасной 
относительно гонок) 
Система 
обещает 
Обеспечить 
иллюзию 
выполнения той 
программы, 
которую вы 
написали. 
(путём компиляции 
и выполнения) 
48
˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ 
Вы обещаете 
Корректно 
реализовать 
синхронизацию 
в вашей 
программе 
(путём добавления 
необходимых 
инструкций в 
программу, 
делающих её 
безопасной 
относительно гонок) 
Система 
обещает 
Обеспечить 
иллюзию 
выполнения той 
программы, 
которую вы 
написали. 
(путём компиляции 
и выполнения) 
Модель памяти определяет, какие действия вы должны 
совершить и как должна отреагировать система, чтобы 
обеспечить выполнение операций с памятью в необходимой 
последовательности. 49
ˑ̀̀˱́˱̃˾˿˶ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶ 
˹˾̂̃́̄˻̇˹˺ 
50
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Алгоритм Деккера позволяет решать проблему взаимного 
исключения и был опубликован в 1965 г. 
Он не приводит к взаимным исключениям (deadlock) и 
свободен от голодания (starvation). 
Поток 1 
flag1 = 1; 
if (flag2 != 0) 
// ожидать освобождения 
// критической секции 
else 
// войти в критическую 
// секцию 
Поток 2 
flag2 = 1; 
if (flag1 != 0) 
// ожидать освобождения 
// критической секции 
else 
// войти в критическую 
// секцию 
51
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Store Buffer Store Buffer 
Память: flag1 = 0, flag2 = 0 
52
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Store Buffer 
Сохранение 
1 в буфере 
53
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
54
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
StoreLoad 
55
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 0, flag2 = 0 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
56
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ 
Процессор 1 Процессор 2 
flag1 = 1; 
if (flag2 != 0) 
{ … } 
Память: flag1 = 1, flag2 = 1 
flag2 = 1; 
if (flag1 != 0) 
{ … } 
Store Buffer 
flag1 = 1 
Сохранение 
1 в буфере 
Сохранение 
1 в буфере 
Store Buffer 
flag1 = 1 
Чтение 0 
для flag2 
Чтение 0 
для flag1 
Сброс буфера 
(flag1) 
Сброс буфера 
(flag2) 
57
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
ˣ˹̀ 
̀˶́˶̄̀˿́̐ 
˵˿̈˹˳˱˾˹̐ 
˱́̆˹̃˶˻̃̄́̌ 
$OSKD $50Y 32:(5 63$5 
502 
63$5 
362 
63$5 
762 
[ $0' ,$ 
/RDGV̀˿̂˼˶ 
ORDGV 
/RDGV̀˿̂˼˶ 
VWRUHV 
6WRUHV̀˿̂˼˶ 
VWRUHV 
6WRUHV̀˿̂˼˶ 
ORDGV 
ˑ˟̂ORDGV 
ˑ˟̂VWRUHV 
˘˱˳˹̂˹˽̌˶ 
ORDGV 
АО - атомарные операции 58
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
59
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
При сильном 
упорядочивании 
(сильная модель памяти) 
каждая инструкция 
неявно реализует 
семантику захвата и 
освобождения. 
При слабом 
упорядочивании 
(слабая модель памяти) 
не накладывается 
никаких ограничений на 
порядок выполнения 
инструкций. 
60
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
61
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Intel Architectures Software Developer’s Manual, Vol. 3: 
▪ Операции чтения не могут быть переупорядочены с другими 
операциями чтения 
▪ Операции записи не могут быть переупорядочены с другими 
операциями записями 
▪ Операции записи не могут быть переупорядочены с другими 
операциями записи, кроме следующих исключений: … 
▪ Операции чтения могут быть переупорядочены с более 
старыми операциями записи в другие области памяти, 
но не с операциями записи в ту же область. 
▪ Операции чтения не могут перейти раньше инструкций LFENCE, 
MFENCE, операции записи - раньше инструкций LFENCE, 
SFENCE, MFENCE. 
▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше 
операций чтения, записи, того или другого соответственно. 
62
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
x = 0, y = 0 
Процессор 1 
mov x, 1 ; запись (load) 
; 1 в x 
mov r1, y ; загрузка (store) 
; из y в регистр 
Процессор 2 
mov y, 1 ; запись (load) 
; 1 в x 
mov r2, x ; загрузка (store) 
; из y в регистр 
Возможные варианты: 
▪ r1 = 0, r2 = 1 
▪ r1 = 1, r2 = 0 
▪ r1 = 1, r2 = 1 
▪ r1 = 0, r2 = 0 
63
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
x = 0, y = 0 
Процессор 1 
mov x, 1 ; запись (load) 
; 1 в x 
StoreLoad 
mov r1, y ; загрузка (store) 
; из y в регистр 
Процессор 2 
mov y, 1 ; запись (load) 
; 1 в x 
mov r2, x ; загрузка (store) 
; из y в регистр 
▪ r1 = 0, r2 = 0 
В процессорах архитектуры x86 достустима перестановка 
операция load после store. Или, то же, операции store могут 
выполняться до операций load. 
64
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Программный барьер памяти: 
запретить переупорядочивание 
инструкций компилятором. 
65
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile( ::: 
printf(reordering happened!n); 
memory); 
r2 = x; 
66 
Переупорядочивание 
может произойти!
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile( ::: 
printf(reordering happened!n); 
memory); 
r2 = x; 
67
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 2 
y = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r2 = x; 
Поток 3 
if (r1 == 0  r2 == 0) 
printf(reordering happened!n); 
68
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r1 = y; 
Аппаратный барьер памяти: 
запретить переупорядочивание 
инструкций процессором. 
Программный барьер памяти: 
запретить переупорядочивание 
инструкций компилятором. 
69 
mov DWORD PTR X[rip], 1 
mfence 
mov eax, DWORD PTR Y[rip] 
... 
mov DWORD PTR r1[rip], eax 
полный барьер памяти
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ 
Поток 1 
x = 1; 
asm volatile(mfence ::: 
memory); 
asm volatile( ::: 
memory); 
r1 = y; 
Поток 3 
if (r1 == 0  r2 == 0) 
Поток 2 
y = 1; 
asm volatile(mfence ::: 
asm volatile( ::: 
r2 = x; 
printf(reordering happened!n); 
memory); 
memory); 
Переупорядочивание 
не произойдёт! 
70
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
более сильное упорядочивание 
(strong model, sequential consistency) 
IBM Blue Gene 
Смартфоны 
DEC Alpha 
Xbox 360 
PowerPC 
ARM 
x86 / 64 
SPARC TSO 
более слабое упорядочивание 
(weak model, relaxed ordering) 
Как правило сильное 
упорядочивание 
Ослабленное упорядочивание 
с поддержкой зависимостей 
по данным 
Вполне 
ослабленное 
упорядочивание 
PowerMac 
71
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Для ослабленного упорядочивания характерно то, что одно ядро может 
видеть изменения в общей памяти в порядке, отличном от порядка, в 
котором другое ядро вносит изменения. 
int sharedCount; // глобальный счётчик 
void IncShared() { 
int count = 0; // локальный счётчик 
while (count  N) { 
randomBusyWork(); // случайная задержка 
// наивная реализация мьютекса 
int expected = 0; 
if (flag.compare_exchange_strong(expected, 1, 
std::memory_order_relaxed)); { 
sharedVal++; // выполняется под защитой мьютекса 
flag.store(0, std::memory_order_relaxed)) 
count++; 
} 
} 
72
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Для ослабленного упорядочивания характерно то, что одно ядро может 
видеть изменения в общей памяти в порядке, отличном от порядка, в 
котором другое ядро вносит изменения. 
int sharedCount; // глобальный счётчик 
void IncShared() { 
int count = 0; // локальный счётчик 
while (count  N) { 
randomBusyWork(); // случайная задержка 
// наивная реализация мьютекса 
int expected = 0; 
if (flag.compare_exchange_strong(expected, 1, 
std::memory_order_relaxed)); { 
sharedVal++; // выполняется под защитой мьютекса 
flag.store(0, std::memory_order_relaxed)) 
count++; 
} 
} StoreStore 
73
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Для ослабленного упорядочивания характерно то, что одно ядро может 
видеть изменения в общей памяти в порядке, отличном от порядка, в 
котором другое ядро вносит изменения. 
int sharedCount; // глобальный счётчик 
void IncShared() { 
int count = 0; // локальный счётчик 
while (count  N) { 
randomBusyWork(); // случайная задержка 
// наивная реализация мьютекса 
int expected = 0; 
if (flag.compare_exchange_strong(expected, 1, 
std::memory_order_relaxed)); { 
sharedVal++; // выполняется под защитой мьютекса 
asm volatile( ::: memory); 
flag.store(0, std::memory_order_relaxed)) 
count++; 
} 
} 
StoreStore 
запрет компиляторного 
переупорядочивания 74
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 1 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
sharedVal++; 
asm volatile(...); 
flag.store(0, ...) 
count++; 
} 
} 
Поток 2 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
sharedVal++; 
asm volatile(...); 
flag.store(0, ...) 
count++; 
} 
} 
75
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 1 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
sharedVal++; 
asm volatile(...); 
(1) flag.store(0, ...); 
count++; 
} 
} 
Поток 2 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
sharedVal++; 
asm volatile(...); 
flag.store(0, ...) 
count++; 
} 
} 
76
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Поток 1 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
(2) sharedVal++; 
asm volatile(...); 
(1) flag.store(0, ...); 
count++; 
} 
} 
Поток 2 
int sharedCount; 
void IncShared() { 
int count = 0; 
while (count  N) { 
randomBusyWork(); 
int expected = 0; 
if (...) { 
(3) sharedVal++; 
asm volatile(...); 
flag.store(0, ...) 
count++; 
} 
} 
77
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ 
Допустим N = 100000 
Каждый из 2 потоков выполняет 100000 операций 
инкремента разделяемой переменной sharedCount++. 
Ожидаемое значение: sharedVal = 200000 
В реальности: 
$ ./prog 100000 // N = 100000 
sharedCount = 199348 
sharedCount = 199034 
sharedCount = 199517 
sharedCount = 199829 
sharedCount = 199113 
sharedCount = 199566 
78
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
79
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
80
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
81
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
Буфер 1 Буфер 2 Буфер N 
L2-кэш 
... ... 
82
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
... ... 
L2-кэш: widget[i].y = y; widget[i].ready = true; 
83
ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ 
̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 
Поток 1 (процессор 1) 
// Подготавливаем данные 
// и устанавливаем StoreStore 
флаг 
// готовности 
for (i = 0; i  n; i++) { 
widget[i].x = x; 
widget[i].y = y; 
widget[i].ready = true; 
} 
Поток 2 (процессор 2) 
// Если данные готовы, 
// обрабатываем их 
for (i = 0; i  n; i++) { 
if (widget[i].ready) { 
do_some_stuff(widget[i]); 
} 
} 
... ... 
L2-кэш: widget[i].y = y; widget[i].ready = true; 
84
ˠ́˿˴́˱˽˽˾˿˶ 
̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶ 
˹˾̂̃́̄˻̇˹˺ 
85
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
Первое правило робототехники компиляторов и 
процессоров при упорядочивании доступа к памяти: 
Нельзя изменять поведение 
однопоточной программы. 
▪ В однопоточных программах переупорядочивания 
остаются незамеченными. 
▪ То же самое - при многопоточном программировании 
на основе мьютексов, семафоров и т.д. 
▪ Но не при использовании атомарных переменных 
и техник программирования без блокировок. 
86
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
add eax, 111 
mov DWORD PTR x[rip], eax 
mov DWORD PTR y[rip], 222 
gcc -S -masm=intel prog.c 
выполнение команды 
x = y + 111 завершено 
выполнение команды 
y = 222 завершено 
87
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
mov edx, 222 
... 
mov DWORD PTR y[rip], 222 
lea esi, [rax+111] 
... 
mov DWORD PTR x[rip], esi 
gcc -S -masm=intel -O2 prog.c 
y = 222 выполняется 
раньше x = y + 111 
выполнение команды 
y = 222 завершено 
выполнение команды 
x = y + 111 завершено 
88
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ 
int x, y; 
int main() { 
x = y + 111; 
asm volatile( ::: memory); 
y = 222; 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
add eax, 111 
mov DWORD PTR x[rip], eax 
mov esi, DWORD PTR x[rip] 
mov edx, 222 
... 
xor eax, eax 
mov DWORD PTR y[rip], 222 
явный барьер 
gcc -S -masm=intel -O2 prog.c 
89
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
int data; 
bool isReleased = false; // данные опубликованы? 
void releaseData(int val) // опубликовать данные 
{ 
data = val; // записать данные 
isReleased = true; // данные опубликованы, 
} // можно с ними работать 
90
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Данные должны быть 
проинициализированы в 1 потоке 
перед тем, как они будут 
использованы во 2 потоке. 
91
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
1. Данные проиницаилизрованы 
92
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
1. Данные проициализированы 
2. Ура! Второй поток может 
обрабатывать 
93
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
94
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Из-за переупорядочивания 
инструкций компилятором флаг 
выставляется до того, как 
данные готовы. 
95
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
StoreStore 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Из-за переупорядочивания 
инструкций компилятором флаг 
выставляется до того, как 
данные готовы. 
96
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
Из-за переупорядочивания 
инструкций компилятором флаг 
выставляется до того, как 
данные готовы. 
97
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
doSomething(data); 
} 
98
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
asm volatile( ::: 
memory); 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
asm volatile( ::: 
memory); 
doSomething(data); 
} 
99
ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺˾˱̀́˹˽˶́˶ 
̂̀̄˲˼˹˻˱̇˹˶˺˵˱˾˾̌̆˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐
Поток 1 
int data; 
bool isReleased = false; 
void releaseData(int val) 
{ 
data = val; 
asm volatile( ::: 
memory); 
isReleased = true; 
} 
Поток 2 
void utilizeData() 
{ 
while (!isReleased); 
asm volatile( ::: 
memory); 
doSomething(data); 
} 
Операция записи- 
освобождения 
Операция чтения- 
захвата 
100
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸̈˹̂̃˿˴˿˳˿˸˵̄̆˱RXWRIWKLQDLUVWRUHV
if (x  0) 
y++; 
register int r = y; 
if (x  0) 
r++; 
y = r; 
Размещение переменной в регистре 
в процессе оптимизирующей 
компиляции 
создание новой регистровой 
переменной 
Новая операция сохранения 
(store) “из чистого воздуха” 
Создание операций сохранения “из чистого 
воздуха” не допустимо в соответствии с 
последним стандартом, однако... 101
˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸̈˹̂̃˿˴˿˳˿˸˵̄̆˱RXWRIWKLQDLUVWRUHV
однако в С  PThreads такая оптимизация допускается: 
static pthread_mutex_t lock = PTHREAD_MUTEX_INITALIZER; 
static int count = 0; 
int trylock() { 
int rc; 
rc = pthread_mutex_trylock(mutex); 
if (rc == 0) 
count++; 
... 
int trylock() { 
register int r = count; 
int rc; 
rc = pthread_mutex_trylock(mutex); 
if (rc == 0) 
count++; 
count = r; 
новый store 
и новая гонка данных! 102
˒˱˸˿˳˿˶˿̃˾˿̉˶˾˹˶ 
KDSSHQVEHIRUH 
̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
103
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; // A 
y = x + 1; // B 
104
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
Все соотношения 
happens-before для 
операции А (x = 10) 
105
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
m = k - 5; 
print(m) 
print(x) 
106
˟̃˾˿̉˶˾˹˶KDSSHQVEHIRUH̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
Поток 1 Поток 2 
x = 10; 
y = x + 1; 
z = sqrt(x * y); 
m = k - 5; 
print(m) 
w = x + 2 
107
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
108
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
int x, y; 
int main() { 
x = y + 111; // A 
y = 222; // B 
printf(%d%d, x, y); 
109
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
int x, y; 
int main() { 
x = y + 111; // A 
y = 222; // B 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
mov edx, 222 
... 
mov DWORD PTR y[rip], 222 
lea esi, [rax+111] 
... 
mov DWORD PTR x[rip], esi 
gcc -S -masm=intel -O2 prog.c 
происходит раньше В. 
110
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
1. Из того, что А происходит-раньше В не следует, что А 
int x, y; 
int main() { 
x = y + 111; 
y = 222; 
printf(%d%d, x, y); 
mov eax, DWORD PTR y[rip] 
mov edx, 222 
... 
mov DWORD PTR y[rip], 222 
lea esi, [rax+111] 
... 
mov DWORD PTR x[rip], esi 
gcc -S -masm=intel -O2 prog.c 
y = 222 выполняется 
раньше x = y + 111 
выполнение команды 
y = 222 завершено 
выполнение команды 
x = y + 111 завершено 
происходит раньше В. 
111
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) // B 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; // A 
112
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) // B 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; // A 
113
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; 
114
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Поток 2 
if (ready) 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready = true; 
115
ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 
Отношение happens-before определяет, какие операции видят 
последствия других операций. 
Допустим A и В - операции в многопоточной программе. Тогда если 
А происходит-раньше В, тогда эффект (результат операции, 
который отразился в памяти) операции А становится видим для 
потока, выполняющего операцию В. 
1. Из того, что А происходит-раньше В не следует, что А 
происходит раньше В. 
2. Из того, что А происходит раньше В не следует, что А 
происходит-раньше В 
Отношение происходит-раньше имеет место тогда (и только 
тогда), когда это определёно стандартом языка. 
116
˓˹˵̌̄̀˿́̐˵˿̈˹˳˱˾˹̐ 
˹˾̂̃́̄˻̇˹˺˳ˢ 
117
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint x{0}, y{0}, v{0}, w{0} 
void thread1() { 
x.store(1, std::memory_order_seq_cst); 
v.store(y.load(std::memory_order_seq_cst), 
std::memory_order_seq_cst); 
} 
void thread2() { 
y.store(1, std::memory_order_seq_cst); 
w.store(x.load(std::memory_order_seq_cst), 
std::memory_order_seq_cst); 
} 
int main() { 
std::thread t1{thread1}, t2{thread2}; 
t1.join(); 
t2.join(); 
assert(v != 0 || w != 0); assert не сработает 
118
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint data{0}; 
std::atomicbool ready{false}; 
int main() { 
std::thread producer{[]{ 
data.store(42, std::memory_order_seq_cst); 
ready.store(true, std::memory_order_seq_cst); 
}}; 
std::thread consumer{[]{ 
while (!ready.load(std::memory_order_seq_cst)) { } 
std::cout  data.load(std::memory_order_seq_cst); 
}}; 
producer.join(); 
consumer.join(); 
42 
119
ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˱̐̂˿˴˼˱̂˿˳˱˾˾˿̂̃̍VHTXHQWLDOFRQVLVWHQF
std::atomicint z{0}; 
std::atomicbool x{false}, y{false}; 
int main(int argc, const char *argv[]) { 
std::thread store_y{[]{ 
x.store(true, std::memory_order_seq_cst); }}; 
std::thread store_x{[]{ 
y.store(true, std::memory_order_seq_cst); }}; 
std::thread read_x_then_y{[]{ 
while (!x.load(std::memory_order_seq_cst)) { } 
if (y.load(std::memory_order_seq_cst)) 
z.fetch_add(1, std::memory_order_seq_cst); }}; 
std::thread read_y_then_x{[]{ 
while (!y.load(std::memory_order_seq_cst)) { } 
if (y.load(std::memory_order_seq_cst)) 
z.fetch_add(1, std::memory_order_seq_cst); }}; 
store_x.join(); store_y.join(); 
read_x_then_y.join(); read_y_then_x.join(); 
std::cout  z.load(std::memory_order_seq_cst); 
z  0 
120
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint x{0}, y{0}, v{0}, w{0} 
void thread1() { 
x.store(1, std::memory_order_relaxed); 
v.store(y.load(std::memory_order_relaxed), 
std::memory_order_relaxed); 
} 
void thread2() { 
y.store(1, std::memory_order_relaxed); 
w.store(x.load(std::memory_order_relaxed), 
std::memory_order_relaxed); 
} 
int main() { 
std::thread t1{thread1}, t2{thread2}; 
t1.join(); 
t2.join(); 
assert(v != 0 || w != 0); assert может 
сработать 121
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint data{0}; 
std::atomicbool ready{false}; 
int main() { 
std::thread producer{[]{ 
data.store(42, std::memory_order_relaxed); 
ready.store(true, std::memory_order_relaxed); 
}}; 
std::thread consumer{[]{ 
while (!ready.load(std::memory_order_relaxed)) { } 
std::cout  data.load(std::memory_order_relaxed); 
}}; 
producer.join(); 
consumer.join(); 
42 не гарантируется 
122
˟̂˼˱˲˼˶˾˾˿˶̄̀˿́̐˵˿̈˶˾˹˶UHOD[HGRUGHULQJ
std::atomicint z{0}; 
std::atomicbool x{false}, y{false}; 
int main(int argc, const char *argv[]) { 
std::thread store_y{[]{ 
x.store(true, std::memory_order_relaxed); }}; 
std::thread store_x{[]{ 
y.store(true, std::memory_order_relaxed); }}; 
std::thread read_x_then_y{[]{ 
while (!x.load(std::memory_order_relaxed)) { } 
if (y.load(std::memory_order_relaxed)) 
z.fetch_add(1, std::memory_order_relaxed); }}; 
std::thread read_y_then_x{[]{ 
while (!y.load(std::memory_order_relaxed)) { } 
if (y.load(std::memory_order_relaxed)) 
z.fetch_add(1, std::memory_order_relaxed); }}; 
store_x.join(); store_y.join(); 
read_x_then_y.join(); read_y_then_x.join(); 
std::cout  z.load(std::memory_order_relaxed); 
z = 0 
123
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
x.load 
(memory_order_relaxed) 
124
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
125 
x.load 
(memory_order_relaxed)
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Иван 
Значение переменной x 
126 
x.load 
(memory_order_relaxed) 
encore!
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
Значение переменной x 
x.load 
(memory_order_relaxed) 
Иван 
127
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
-8 
Иван 
Значение переменной x 
x.store(-8, 
memory_order_relaxed) 
128
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
34 
9 
45 
-3 
21 
097 
11 
-8 
Значение переменной x 
x.load 
(memory_order_relaxed) 
Иван 
129
˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ 
Иван 
Сергей 
Анна 
... 
34 
9 
45 
-3 
21 
097 
11 
-8 
Иван 
Анна Сергей 
Значение переменной x 
130
˒˱́̍˶́̌̀˱˽̐̃˹ 
131
˓˹˵̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˺ 
LoadLoad LoadStore 
StoreLoad StoreStore 
132
˙˶́˱́̆˹̈˶̂˻˱̐̂̃́̄˻̃̄́˱̂˿˳́˶˽˶˾˾̌̆˽˾˿˴˿̐˵˶́˾̌̆̂˹̂̃˶˽ 
кэш 2 
кэш 1 
Память 
кэш 2 
кэш 1 кэш 1 кэш 1 
Процессорные 
ядра 
133
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
кэш L1 
2 
кэш L1 
Память 
134
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
кэш L1 
2 
кэш L1 
Память 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
135
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Центральный 
репозиторий 
136
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
137
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 0 
x = 0 
138
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 1 
x = 0 
139
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Утечка данных из 
центрального 
репозитория в 
локальный и обратно 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = 1 
x = 1 
140
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
x = 1 
x = ? 
x = ? 
Неизвестно, когда 
изменения распространятся 
на другие потоки. 
141
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 0 
y = 0 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 0 
x = 0 
y = 0 
142
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 1 
y = 0 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 1 
x = 0 
y = 0 
143
˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 
1 
2 
x = 1 
y = ? {0,1} 
Рекс 
Мухтар 
Центральный 
репозиторий 
mov [x], 1 
mov r1, [y] 
mov [y], 1 
mov r2, [x] 
x = ? {0,1} 
y = 1 
x = ? {0,1} 
y = ? {0,1} 
144
˓˹˵̌˲˱́̍˶́˿˳ 
LoadLoad LoadStore 
StoreLoad StoreStore 
145
˒˱́̍˶́/RDG/RDG 
1 
Рекс 
Центральный 
репозиторий 
LoadLoad 
x = 1 
y = 0 
x = 1 
y = 0 
git pull, hg pull, 
svn update, 
cvs update 
Барьер LoadLoad: 
▪ Предотвращает переупорядочивания между 
загрузками до барьера и загрузками после 
барьера. 
▪ Гарантирует, что загруженные из центрального 
репозитория (памяти) в локальный репозиторий 
(кэш) значения будут по крайней мере такие же 
новые, как и последнее значение, которое 
“просочилось” из центрального репозитория. 146
˒˱́̍˶́/RDG/RDG 
1 
Рекс 
Центральный 
репозиторий 
LoadLoad 
x = 1 
y = 0 
x = 1 
y = 0 
git pull, hg pull, 
svn update, 
cvs update 
// Так получилось, что 
// widget.ready = true утекло 
// в локальный репозиторий 
if (widget.ready) { 
// Подгрузить всё остальное 
// содержимое widget - 
// такое же “свежее”, как ready 
LoadLoadFence(); 
do_something(widget); 
} 147
˒˱́̍˶́6WRUH6WRUH 
1 
Рекс 
Центральный 
репозиторий 
StoreStore 
x = 1 
y = 0 
Барьер StoreStore: 
▪ Предотвращает переупорядочивания между 
сохранениями до барьера и сохранениями после 
барьера. 
▪ Гарантирует, что загруженные из локального 
репозитория (кэш) в локальный репозиторий 
(память) значения будут по крайней мере такие 
же новые, как и последнее значение, которое 
“просочилось” из локального репозитория. 
x = 1 
y = 0 
git push, hg push, 
svn commit, cvs 
commit 
148
˒˱́̍˶́6WRUH6WRUH 
1 
Рекс 
Центральный 
репозиторий 
StoreStore 
x = 1 
y = 0 
x = 1 
y = 0 
git push, hg push, 
svn commit, cvs 
commit 
widget.x = x; 
widget.y = y; 
StoreStoreFence(); 
widget.ready = true; 
// Если ready = true просочится 
// Мухтару, то он увидит увидит 
// на центральном репозитории 
// всё остальные поля widget, 
// которые для него подготовил Рекс 149
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Операции загрузки 
load 
Операции сохранения 
store 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
150
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Операции загрузки 
load - отложены 
Операции сохранения 
store - выполнены 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, то он 
просматривает следующие операции сохранения, 
и если они абсолютно не связаны с текущей 
операцией загрузки, то он откладывает 
выполнение операции загрузки и в первую 
очередь выполняет операции сохранения. 151
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Будут промахи по 
кэшу... 
Будут попадания в 
кэш... 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, которые 
промахиваются по кэшу, то он просматривает следующие 
операции сохранения, которые попадают в кэш, и если 
они абсолютно не связаны с текущей операцией загрузки, 
то он откладывает выполнение операции загрузки и в 
первую очередь выполняет операции сохранения. 152
˒˱́̍˶́/RDG6WRUH 
1 
mov r1, [y] 
mov r2, [x] 
LoadStore 
mov [z], 42 
mov [w], r3 
mov [v], r4 
Рекс 
Будут промахи по 
кэшу... 
Будут попадания в 
кэш... 
Переупорядочивание LoadStore 
1. Есть набор инструкций, состоящий из операций 
сохранения и загрузки. 
2. Если Рекс встречает операцию загрузки, которые 
промахиваются по кэшу, то он просматривает следующие 
операции сохранения, которые попадают в кэш, и если 
они абсолютно не связаны с текущей операцией загрузки, 
то он откладывает выполнение операции загрузки и в 
первую очередь выполняет операции сохранения. 153
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
mov r1, [y] 
x = 1 
y = 0 
Рекс 
2 
mov [y], 1 
mov r2, [x] 
x = 0 
y = 1 
Мухтар 
r1 = 0 
r2 = 0 
154
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
2 
mov [y], 1 
StoreLoad 
mov r2, [x] 
x = ? 
y = 1 
Мухтар 
Барьер StoreLoad: 
▪ Гарантирует видимость для других 
процессоров всех операций 
сохранения, выполненных до 
барьера. 
▪ Обеспечивает для всех операций 
загрузки, выполненных после 
барьера, получение результатов, 
которые имеют место во время 
барьера. 
▪ Барьер предотвращает r1 = r2 = 0 
▪ StoreLoad ≠ StoreStore + 
LoadLoad 
155
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
156
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
2. Ожидание завершения выполнения операции отправки (в 
отличие от StoreStore, который может выполняться с задержкой). 
157
˒˱́̍˶́6WRUH/RDG 
1 
mov [x], 1 
StoreLoad 
mov r1, [y] 
x = 1 
y = ? 
Рекс 
Центральный 
репозиторий 
Барьер StoreLoad (≠ StoreStore + LoadLoad): 
1. Отправка (push) всех изменений в центральный репозиторий. 
2. Ожидание завершения выполнения операции отправки (в 
отличие от StoreStore, который может выполняться с задержкой). 
3. Загрузка (pull) всех последних изменений из центрального 
репозитория (в отличие от LoadLoad, который не загружает 
абсолютно последние изменения) 
158
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃ 
˿̂˳˿˲˿˷˵˶˾˹˶DFTXLUH 
UHOHDVHVHPDQWLFV
159
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ 
Семантика захвата (acquire) 
▪ Применяется к операциям чтения или чтения- 
модификации-записи, при этом такая операция 
становится операцией чтения-захвата (read-acquire). 
▪ Предотвращает переупорядочивание инструкции чтения- 
захвата и всех следующих в программе операций чтения 
или записи. 
▪ Применяется к операциям записи или чтения- 
модификации-записи, причём такая операция становится 
операцией записи-освобождения (write-release). 
▪ Предотвращает переупорядочивание инструкции записи- 
освобождения со всеми предшествующими в программе 
операциями чтения или записи. 
Семантика освобождения (release) 160
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ 
read-acquire 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
write-release 161
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ 
read-acquire 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
write-release 162
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
Acquire (захват) 
LoadLoadFence LoadStoreFence 
StoreLoadFence StoreStoreFence 
Release (освобождение) 
163
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 164
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 165
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 166
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 
1 
2 
167
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
StoreStoreFence + LoadStoreFence 
write-release 
2 
1 
1 
2 
168
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ 
read-acquire 
LoadLoadFence + LoadStoreFence 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
read / write 
LoadStoreFence + StoreStoreFence 
write-release 169
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
170
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
Synchronizes-with 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Синхронизируется-с 
Всё, что 
здесь... 
… будет 
видно здесь 
171
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 1 
x = 42 
ready.store(true, 
memory_order_release); 
Synchronizes-with 
Поток 2 
ready.load( 
memory_order_acquire); 
print(x); 
int x; 
bool ready = false; 
Синхронизируется-с 
… будет 
видно здесь 
Всё, что 
здесь... 
Операция записи- 
освобождения 
Операция чтения- 
захвата 
172
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Поток 1 
void initWidget(x, y, z) { 
w.x = x; 
w.y = x; 
w.z = z; 
ready.store(true, 
memory_order_release); 
} 
Поток 2 
void useWidget() { 
while (!ready.load( 
memory_order_acquire)) 
{} 
doSomething(w); 
} 
widget w; 
bool ready = false; 
173
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 2 
void useWidget() { 
while (!ready.load( 
memory_order_acquire)) 
{} 
doSomething(w); 
} 
widget w; 
bool ready = false; 
Поток 1 
void initWidget(x, y, z) { 
w.x = x; 
w.y = x; 
w.z = z; 
ready.store(true, 
memory_order_release); 
} 
Синхронизируется-с 
Всё, что 
здесь... 
… будет 
видно 
здесь 
Операция записи- 
освобождения 
Операция 
чтения-захвата 
174
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 2 
void useWidget() { 
while (!ready.load( 
memory_order_acquire)) 
{} 
s1 = to_string(w); 
s2 = number  + s1; 
print(s2); 
} 
int x, y, z; 
bool ready = false; 
Поток 1 
void initWidget(x, y, z) { 
x = 10 
y = x + 20; 
z = x + 12; 
ready.store(true, 
memory_order_release); 
} 
Синхронизируется-с 
175
ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ 
Synchronizes-with 
Поток 2 
void useWidget() { 
while (!ready.load( 
memory_order_acquire)) 
{} 
s1 = to_string(w); 
s2 = number  + s1; 
print(s2); 
} 
int x, y, z; 
bool ready = false; 
Поток 1 
void initWidget(x, y, z) { 
x = 10 
y = x + 20; 
z = x + 12; 
ready.store(true, 
memory_order_release); 
} 
Синхронизируется-с 
Операция записи- 
освобождения 
Операция 
чтения-захвата 
176
˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳ˢ 
std::memory_order_acquire 
▪ Применяется к операциям чтения или чтения- 
модификации-записи: load, compare_exchange, 
fetch_add, fetch_or, etc. 
▫ x.load(std::memory_order_acquire); 
▫ compare_exchange_weak(x, y, 
std::memory_order_acquire); 
▪ Применяется к операциям записи или чтения- 
модификации-записи: store, operator=, load, 
compare_exchange, fetch_add, fetch_or, etc 
▫ x.store(42, std::memory_order_release); 
▫ compare_exchange_weak(x, y, 
std::memory_order_release); 
std::memory_order_release 177
˟̃˾˿̉˶˾˹˶ 
̂˹˾̆́˿˾˹˸˹́̄˶̃̂̐̂ 
VQFKURQL]HGZLWK
178
˟̃˾˿̉˶˾˹˶VQFKURQL]HGZLWK̂˹˾̆́˿˾˹˸˹́̄˶̃̂̐̂
Отношение syncrhonized-with более сильное по сравнению с happens-before, 
т.е.: 
synchronizes-with ⟶ happens-before 
179
˟̃˾˿̉˶˾˹˶VQFKURQL]HGZLWK̂˹˾̆́˿˾˹˸˹́̄˶̃̂̐̂
Отношение syncrhonized-with более сильное по сравнению с happens-before, 
т.е.: 
synchronizes-with ⟶ happens-before 
Операция записи A над переменной x синхронизируется-с такой 
операцией операцией чтения B над x, которая читает значение, 
сохранённое 
1. или операцией A. 
2. или следующей за A операцией записи над x в том же потоке, 
который выполнил A. 
3. или последовательностью операций чтения-модификации-записи 
над x в любом потоке, при условии, что значение, прочитанное 
первым потоком в этой последовательности, является значением, 
записанным операцией A. 
180
˟̃˾˿̉˶˾˹˶VQFKURQL]HGZLWK̂˹˾̆́˿˾˹˸˹́̄˶̃̂̐̂
Отношение syncrhonized-with более сильное по сравнению с happens-before, 
т.е.: 
synchronizes-with ⟶ happens-before 
Операция записи A над переменной x синхронизируется-с такой 
операцией операцией чтения B над x, которая читает значение, 
сохранённое 
1. или операцией A. 
2. или следующей за A операцией записи над x в том же потоке, 
который выполнил A. 
3. или последовательностью операций чтения-модификации-записи 
над x в любом потоке, при условии, что значение, прочитанное 
первым потоком в этой последовательности, является значением, 
записанным операцией A. 
181
˟̃˾˿̉˶˾˹˶VQFKURQL]HGZLWK̂˹˾̆́˿˾˹˸˹́̄˶̃̂̐̂
Отношение syncrhonized-with более сильное по сравнению с happens-before, 
т.е.: 
synchronizes-with ⟶ happens-before 
Операция записи A над переменной x синхронизируется-с такой 
операцией операцией чтения B над x, которая читает значение, 
сохранённое 
1. или операцией A. 
2. или следующей за A операцией записи над x в том же потоке, 
который выполнил A. 
3. или последовательностью операций чтения-модификации-записи 
над x в любом потоке, при условии, что значение, прочитанное 
первым потоком в этой последовательности, является значением, 
записанным операцией A. 
Иначе: Если поток 1 сохраняет значение, а поток 2 читает это 
значение, то существует отношение синхронизируется-с между 
операциями сохранения и загрузки. 182
˟̀˶́˱̇˹$FTXLUHUHOHDVHVQFKURQL]HGZLWK
˳ˢ 
Поток 1 
void prepare() { 
w.x = x; 
w.y = x; 
cntr++; 
w.ready.store(true, 
memory_order_release); 
} 
Synchronizes-with 
Поток 2 
void utilize() { 
while (!w.ready.load( 
memory_order_acquire)) 
{} 
doSomethingWith(w); 
writeLog(cntr); 
} 
Widget w; 
int cntr; 
Синхронизируется-с 
183
˟̀˶́˱̇˹$FTXLUHUHOHDVHVQFKURQL]HGZLWK
˳ˢ 
Поток 1 
void prepare(Widget w) { 
w.x = x; 
w.y = x; 
cntr++; 
w.ready.store(true, 
memory_order_release); 
} 
Synchronizes-with 
Поток 2 
void utilize() { 
if (!w.ready.load( 
memory_order_acquire)){ 
doSomethingWith(w); 
writeLog(cntr); 
} 
Widget w; 
int cntr; 
Синхронизируется-с 
184
˒˱́̍˶́̌DFTXLUHUHOHDVHVQFKURQL]HGZLWK
˳ˢ 
Поток 2 
void utilize() { 
bool ready = w.ready.load( 
memory_order_relaxed); 
if (ready) { 
atomic_thread_fence( 
memory_order_acquire)){ 
doSomethingWith(w); 
writeLog(cntr); 
} 
Widget w; 
int cntr; 
Поток 1 
void prepare(Widget w) { 
w.x = x; 
w.y = x; 
cntr++; 
atomic_thread_fence(, 
memory_order_release); 
w.ready.store(true, 
memory_order_relaxed); 
} 
185
˒˱́̍˶́̌DFTXLUHUHOHDVHVQFKURQL]HGZLWK
˳ˢ 
Рекс 
Мухтар 
Центральный 
репозиторий (ЦР) 
186
˒˱́̍˶́̌DFTXLUHUHOHDVHVQFKURQL]HGZLWK
˳ˢ 
Рекс 
Мухтар 
Центральный 
репозиторий (ЦР) 
Запись в w, 
cntr 
1 
187
˒˱́̍˶́̌DFTXLUHUHOHDVHVQFKURQL]HGZLWK

ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель па

  • 1.
  • 2.
    ˜˶˻̇˹̐ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ ˓˾˶˿̈˶́˶˵˾˿˶˳̌̀˿˼˾˶˾˹˶˹˾̂̃́̄˻̇˹˺ ˒˱́̍˶́̌̀˱˽̐̃˹ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱ ˿̂˳˿˲˿˷˵˶˾˹̐˝˿˵˶˼̍̀˱˽̐̃˹ ˠ˱˸˾˹˻˿˳ˑ˼˶˻̂˶˺ˑ˼˶˻̂˱˾˵́˿˳˹̈ ˛˱̅˶˵́˱˳̌̈˹̂˼˹̃˶˼̍˾̌̆̂˹̂̃˶˽ˢ˹˲˔ˤˣ˙ ˢ˱˺̃˻̄́̂˱KWWSFSFWVLEVXWLVUXaDSD]QLNRYWHDFKLQJ ˓˿̀́˿̂̌KWWSVSLD]]DFRPVLEVXWLVUXIDOOSFWKRPH
  • 3.
  • 4.
    ˑ̃˿˽˱́˾̌˶˿̀˶́˱̇˹˹ ▪ Операциянад разделяемой переменной атомарная, если она выполняется потоком за один неделимый шаг. Ни один из других потоков не может обнаружить эту переменную в промежуточном состоянии. ▪ Если операции, которые совершают потоки над раздялемыми переменными, не атомарны, то это приведёт к гонкам данных. ▪ Гонки данных являются причиной неопредлённого поведения, поскольку они приводят к частичным (фрагментированным, “разорванным”) чтениям и записям переменных. 3
  • 5.
    ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 запись младших 32 бит запись старших 32 бит ▪ Выполнение присваивания 64-битного целого shared = 42 на 32-разрядной архитектуре выполняется за 2 инструкции. ▪ Операция записи не атомарная. 4
  • 6.
    ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 1 поток запись старших 32 бит ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти и будут использованы другими потоками. ▪ На многоядерных системах даже не требуется вытеснения. 5
  • 7.
    ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; int main() { shared = 0x100000002; ... gcc -m32 -S -masm=intel -O1 prog.c mov DWORD PTR shared, 2 mov DWORD PTR shared+4, 1 1 поток 2 поток ▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти, а старшие будут записаны другим потоком. ▪ На многоядерных системах даже не требуется вытеснения. 6
  • 8.
    ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ uint64_t shared; uint64_t getShared() { return shared; } gcc -m32 -S -masm=intel -O1 prog.c mov eax, DWORD PTR shared mov edx, DWORD PTR shared+4 ret чтение младших 32 бит чтение старших 32 бит ▪ Выполнение чтения 64-битного целого shared на 32- разрядной архитектуре выполняется за 2 инструкции. ▪ Операция чтения не атомарная. 7
  • 9.
    ˥́˱˴˽˶˾̃˹́˿˳˱˾˾̌˶̈̃˶˾˹̐˹˸˱̀˹̂˹ ▪ Инструкциимогут быть неатомарными, даже если выполняются одной процессорной инструкцией. Например, в ARMv7 инструкция для помещения содержимого двух 32-битных регистров в один 64-битный: strd r0, r1, [r2] На некоторых процессорах эта инструкция реализуются двумя отдельными операциями сохранения. 32-битная операция mov атомарна только для выравненных данных. В остальных случаях операция неатомарная ▪ Вытеснение потоков, выполняющих данные операции, или выполнение операций в двугих потоках в многоядерных системах приводит к неопределённому поведению. 8
  • 10.
    ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ ▪ Вязыках С и С++ предполагается, что все операции неатомарны. ▪ Операции могут быть атомарными в большинстве случаев, например, операция присваивания 32-битного целого значения. ▪ Тем не менее, все инструкции должны рассматриваться как неатомарные. ▪ К счастью, в С и С++ есть набор шаблонов атомарных типов данных. 9
  • 11.
    ˑ̃˿˽˱́˾˿̂̃̍˳ˢ˹ˢ ▪ Атомарныеоперации в С++ неделимы. Из любого потока нельзя обнаружить эту операцию выполненной частично - она либо выполнена, либо невыполнена. ▪ Это позволяет избежать гонок данных. ▪ Для атомарных типов определён метод is_lock_free, позволяющий определить, являются ли операции над ним напрямую с помощью атомарных инструкций, или они эмулируются. 10
  • 12.
    ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ ▪ std::atomic_flag- единственный тип, который не имеет функции is_lock_free. Он предельно простой, всецело атомарный и поддерживает одну операцию: test_and_set - проверить и установить. ▪ Остальные типы определяются специализацией шаблона std::atomic, например std::atomicint и std:: atomicvoid* 11
  • 13.
    ˑ̃˿˽˱́˾̌˶̃˹̀̌˳ˢ˹ˢ Атомарные типСоответствующая специализация std::atomic_bool std::atomicbool std::atomic_char std::atomicchar std::atomic_schar std::atomicsigned char std::atomic_uchar std::atomicunsigned char std::atomic_short std::atomicshort std::atomic_ushort std::atomicunsigned short std::atomic_int std::atomicint std::atomic_uint std::atomicunsigned int std::atomic_long std::atomiclong ... + пользовательские типы 12
  • 14.
    ˟̀˶́˱̇˹˹˾˱˵˱̃˿˽˱́˾̌˽˹̃˹̀˱˽˹ ▪ Операциисохранения: store, clear, etc. Упорядочение memory_order_relaxed, memory_order_release, memory_order_seq_cst. ▪ Операции загрузки: load, etc. Упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst. ▪ Операции чтения-модификации-записи: compare_exchange, fetch_add, test_and_set, etc. Упорядочение memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_seq_cst, memory_order_acq_rel. 13
  • 15.
    ˑ̃˿˽˱́˾̌˺̅˼˱˴VWGDWRPLFBIODJ std::atomic_flag долженбыть проиниализирован: std::atomic_flag flag = ATOMIC_FLAG_INIT Очистить флаг (операция сохранения): установить значение false: flag.clear(std::memory_order_release); Установить значение флага в true и вернуть предыдущее значение: bool x = flag.test_and_set(); Для атомарного флага запрещены операции копирования и присваивания. 14
  • 16.
    ˡ˶˱˼˹˸˱̇˹̐̂̀˹˾˼˿˻˱˾˱˿̂˾˿˳˶˱̃˿˽˱́˾˿˴˿̅˼˱˴˱ class spinlock_mutex { std::atomic_flag flag; public: spinlock_mutex(): flag{ATOMIC_FLAG_INIT} { } void lock() { while (flag.test_and_set( std::memory_order_acquire)); } void unlock() { flag.clear(std::memory_order_release); } }; n.b. Можно использовать с lock_guard и unique_guard! 15
  • 17.
    ˟̀˶́˱̇˹˹̂˿̆́˱˾˶˾˹̐˹˸˱˴́̄˸˻˹˱̃˿˽˱́˾̌̆̃˹̀˿˳ // Объявитьпеременную и проициализировать true std::atomicbool b(true); // Загрузить значение в переменной в неатомарную // переменную x bool x = b.load(std::memory_order_acquire); // Записать в переменную b значение true b.store(true); // Обменять значение переменной b со значением false, // вернуть предыдущее значение b в переменную x. x = b.exchange(false, std::memory_order_acq_rel); 16
  • 18.
    ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r Операция compare_exchange(“сравнить и обменять”): 1. Сравнить текущее значение атомарной переменной с ожидаемым expected. 2. Если значения совпали, сохранить новое значение desired и вернуть true. 3. Если значения не совпадают, то ожидаемое значение expected заменяется фактическим значением переменной, функция возвращает false. bool compare_exchange_weak(T expected, T desired, std::memory_order order = std::memory_order_seq_cst); bool compare_exchange_strong(T expected, T desired, std::memory_order order = std::memory_order_seq_cst); 17
  • 19.
    ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r compare_exchange_weak() -сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. Значение переменной не изменится, функция возвращает false. Последнее возможно при отсутствии аппаратной поддержки команды сравнить-и-обменять, из-за того, что поток может быть вытеснен в середине требуемой последовательности команд (ложный отказ). Из-за возможного ложного отказа функцию compare_exchange_weak() обычно вызывают в цикле: bool expected = false; extern atomicbool b; ... while (!b.compare_exchange_weak(expected, true); 18
  • 20.
    ˟̀˶́˱̇˹̐q̂́˱˳˾˹̃̍˹˿˲˽˶˾̐̃̍r compare_exchange_strong() гарантируетзамену переменной в случае выполнения условия. ▪ compare_exchange_strong() выгодно использовать в случае однократном выполнении операции, т.е. при необходимости заменить значение на жалаемое, и в случае, если вычисление нового значения занимает длительное время. ▪ Если функция compare_exchange вызывается в цикле, тогда предпочтительнее использовать compare_exchange_weak, чтобы избежать двойного цикла (compare_exchange_strong реализован в виде цикла на системах, которые не поддерживают атомарной операции сравнения и замены). 19
  • 21.
    ˑ̃˿˽˱́˾̌˺̃˹̀DWRPLF7 ! Функции длятипа atomicT*: ▪ is_lock_free, load, store, exchange, compare_exchange_weak, compare_exchange_strong ▪ обменять и прибавить: fetch_add, operator++, operator+= ▪ обменять и вычесть: fetch_sub, operator--, operator-= class C {}; C arr[10]; std::atomicC* ptr(arr); C* x = ptr.fetch_add(2); assert(x == arr); assert(p.load() == some_array[2]); p--; x = p; assert(x == arr[1]); операции чтения- модификации- записи 20
  • 22.
    ˢ̃˱˾˵˱́̃˾̌˶˱̃˿˽˱́˾̌˶̇˶˼˿̈˹̂˼˶˾˾̌˶̃˹̀̌ Функции длятипов стандартных атомарных целочисленных типов (int, unsigned int, long, unsigned long, etc): ▪ is_lock_free, load, store, exchange, compare_exchange_weak, compare_exchange_strong ▪ обменять и прибавить (fetch_add, operator++, operator+=) обменять и вычесть (fetch_sub, operator--, operator-=) ▪ operator=, operator|=, operator^= Отсутствуют операции: ▫ умножения, деления, сдвига 21
  • 23.
    ˠ˿˼̍˸˿˳˱̃˶˼̍̂˻˹˶˱̃˿˽˱́˾̌˶̃˹̀̌ В качествешаблона std::atomic может выступать тип, он должен удовлетворять требованиям ▪ В нём должен присутствовать тривиальный оператор присваивания. Нет виртуальных функций и виртуальных базовых классов, а оператор присваивания генерируется автоматически (например, memcpy) ▪ Тип должен допускать побитовое сравнение на равенство (например, с помощью memcmp) 22
  • 24.
  • 25.
    ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ x =y = v = w = 0 thread1() { x = 1 v = y } thread2() { y = 1 w = x } main() { start_threads(thread1, thread2) join_all_threads() assert(v != 0 || w != 0) assert может сработать! 24
  • 26.
    ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ ready =false producer() { data = 42 ready = true } consumer() { while (!ready) { } print(data) } data = 42 не гарантируется 25
  • 27.
    ˠ́˹˽˶́̌̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹̐˳ˢ x =y = false, z = 0 thread_store_x() { x = true } thread_store_y() { y = true } thread_read_x_then_y() { while (!x) { } if (y) z++ } thread_read_y_then_x() { while (!y) { } if (x) z++ } main { run_all_threads(store_x, store_y, read_x_then_y, read_y_then_x) join_all_threads() assert(z != 0) assert может сработать! 26
  • 28.
  • 29.
    ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ликомпьютер программу, которую вы написали? 28
  • 30.
    ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ликомпьютер программу, которую вы написали? НЕТ 29
  • 31.
    ˟˵˹˳˾̌˺˾˿˳̌˺˹̀́˶˻́˱̂˾̌˺̀˱́˱˼˼˶˼̍˾̌˺˽˹́ Выполняет ликомпьютер программу, которую вы написали? НЕТ Просто дело в том что... иерархическая структура памяти, внеочередное выполнение команд процессора и компиляторная оптимизация. 30
  • 32.
    ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 31
  • 33.
    ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Когерентность кэша (MESI, MOESI, MESIF) Процессорные ядра 32
  • 34.
    ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Когерентность кэша (MESI, MOESI, MESIF) Внеочередное выполнение инструкций (out-of- order execution) Процессорные ядра 33
  • 35.
    ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ Intel 64CISC macro-instructions Instruction Fetch PreDecode Instruction Queue (IQ) Decode Rename/Allocate Retirement Unit (Re-Order Buffer) Scheduler Reservation Stations Execution Units Intel 64 CISC macro-instr. Execution Engine (out-of-order) ITLB Instruction Cache (32KiB) L2 TLB L2 Cache (256 KiB, 8-way) DTLB Data Cache (32KiB) L3 Cache Front-End Pipeline (in-order) Nehalem RISC micro-operations ,QWHO1HKDOHPRUH 3LSHOLQH 34
  • 36.
    ˑ̀̀˱́˱̃˾˿˳̌˸˳˱˾˾̌˶̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ 16 ITLBL1 I-cache (32 KiB, 4-way) byte/cycle Instruction Fetch Unit (IFU) Pre Decode, Prefetch Buffer, Instruction Length Decoder Instruction Queue (IQ) (18 entry – 18 instruction max.) Instruction Decoding Unit (IDU) 3 simple + 1 complex Simple Complex Simple Simple micro-cod Decoded Instruction Queue (DIQ, 28 uops. max) Loop Stream Detection, Micro-Fusion, Macro- Fusion Intel64 CISC macro-instr. Nehalem RISC micro-operations 4 micro-ops. /cycle Unified L2- Cache 6 instr./cycle Branch Prediction Unit (BPU) 5 instructions/cycle 4 uops./cycle 35
  • 37.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▪ Межпроцедурная оптимизация 36
  • 38.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ ▪ Peephole-оптимизация ▪ Локальная оптимизация ▪ Внутрипроцедурная оптимизация ▪ Оптимизация циклов ▫ Анализ индуктивных переменных ▫ Деление цикла на части ▫ Объединение циклов ▫ Инверсия цикла ▫ Расщепление цикла ▪ Межпроцедурная оптимизация 37
  • 39.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐˳˹˵̌˿̀̃˹˽˹˸˱̇˹˺ -O -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -O2 -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -fthread-jumps -falign-functions -falign-jumps -falign-loops -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip-blocks -fdelete-null-pointer-checks -fdevirtualize -fdevirtualize-speculatively -fexpensive-optimizations -fgcse -fgcse-lm -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-cp -fipa-sra -fipa-icf -fisolate-erroneous-paths-dereference -foptimize-sibling-calls -foptimize-strlen -fpartial-inlining -fpeephole2 -freorder-blocks -freorder-blocks- and-partition -freorder-functions -frerun-cse-after-loop -fsched-interblock -fsched-spec -fschedule-insns -fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-builtin-call-dce -ftree-switch-conversion - ftree-tail-merge -ftree-pre -ftree-vrp -fuse-caller-save -O3 -fauto-inc-dec -fbranch-count-reg -fcombine-stack-adjustments -fcompare-elim -fcprop-registers -fdce -fdefer-pop -fdelayed-branch -fdse -fforward-propagate -fguess-branch-probability -fif-conversion2 -fif-conversion -finline-functions-called-once -fipa-pure-const -fipa-profile -fipa-reference -fmerge-constants -fmove-loop-invariants -fshrink-wrap -fsplit-wide-types -ftree-bit-ccp -ftree-ccp -fssa-phiopt -ftree-ch -ftree-copy-prop -ftree-copyrename -ftree-dce -ftree-dominator-opts -ftree-dse -ftree-forwprop -ftree-fre -ftree-phiprop -ftree-sink -ftree-slsr -ftree-sra -ftree-pta -ftree-ter -funit-at-a-time -fthread-jumps -falign-functions -falign-jumps -falign-loops -falign-labels -fcaller-saves -fcrossjumping -fcse-follow-jumps -fcse-skip-blocks -fdelete-null-pointer-checks -fdevirtualize -fdevirtualize-speculatively -fexpensive-optimizations -fgcse -fgcse-lm -fhoist-adjacent-loads -finline-small-functions -findirect-inlining -fipa-cp -fipa-sra -fipa-icf -fisolate-erroneous-paths-dereference -foptimize-sibling-calls -foptimize-strlen -fpartial-inlining -fpeephole2 -freorder-blocks -freorder-blocks-and- partition -freorder-functions -frerun-cse-after-loop -fsched-interblock -fsched-spec -fschedule-insns -fschedule-insns2 -fstrict-aliasing -fstrict-overflow -ftree-builtin-call-dce -ftree-switch-conversion -ftree-tail- merge -ftree-pre -ftree-vrp -fuse-caller-save -finline-functions -funswitch-loops -fpredictive-commoning -fgcse-after-reload -ftree-loop-vectorize -ftree-loop-distribute-patterns -ftree-slp-vectorize -fvect-cost-model -ftree-partial-pre -fipa-cp-clone 38
  • 40.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ x =1; y = 2; x = 3; for (i = 0; i n; i++) sum += a[i]; y = 2; x = 3; r1 = sum; for (i = 0; i n; i++) r1 += a[i]; sum = r1; for (i = 0; i n; i++) j = 42 * i; j = -42 for (i = 0; i n; i++) j = j + 42; 39
  • 41.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ x =Johann; y = Sebastian; z = Bach; for (i = 0; i n; i++) for (j = 0; j m;j++); a[i][j] = 1; z = Bach; x = Johann; y = Sebastian; for (j = 0; j n; j++) for (i = 0; i m; i++); a[i][j] = 1; for (i = 0; i n; i++) a[i] = 1; for (i = 0; i n; i++) b[i] = 2; for (i = 0; i n; i++) { a[i] = 1; b[i] = 2; } 40
  • 42.
    ˛˿˽̀˹˼̐̃˿́˾˱̐˿̀̃˹˽˹˸˱̇˹̐̀́˹˽˶́̌˿̀̃˹˽˹˸˱̇˹˺ Что компиляторзнает: Все операции с памятью совершаются в текущем потоке, что они в точности означают, и какие существуют зависимости по данным. Что компилятор не знает: Какие области памяти доступны и изменяются в разных потоках. Как решить: Сказать! Как-то пометить операции, которые выполняются с разделяемыми переменными. 41
  • 43.
    ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Кэш частные и общие кэши, буферы записи, ... Реальное выполнение программы “Гораздо лучше выполнять другую программу - не ту, что вы написали. Вам на самом деле даже не хочется выполнять эту вашу чушь - вы хотите запускать другую программу! Всё это делается для вашего блага.” 42
  • 44.
    ˮ̃˱̀̌̃́˱˾̂̅˿́˽˱̇˹˹̀́˿˴́˱˽˽̌ ▪ Какправило нельзя определить, на каком уровне произошла трансформация. ▪ Трансформации на всех уровнях эквивалентны, что позволяет рассматривать их как переупорядочивание операций загрузки (loads) и записи (stores). ▪ Необходимое условие при выполнении трансформаций - сохранение иллюзии последовательно согласованного кода. 43 Исходный код Компилятор удаление подвыражений, свёртка констант, оптимизация циклов, ... Процессор предвыборка, спекулятивное выполнение инструкций, буферизация, HTM, ... Кэш частные и общие кэши, буферы записи, ... Реальное выполнение программы
  • 45.
  • 46.
    “Результат выполнения программытакой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. 44
  • 47.
  • 48.
    “Результат выполнения программытакой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! 45
  • 49.
  • 50.
    “Результат выполнения программытакой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979) ▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров. ▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой. И это прекрасно! но...46
  • 51.
  • 52.
    … но выэтого не хотите! ▪ Скорее всего очень нерационально выполнять в точности то, что вы написали. ▪ Гораздо лучше выполнить нечто иное, которое бы работало так же, как и то, что вы написали, но выполнялось бы гораздо быстрее. Поэтому ▪ Мы (программное обеспечение ПО: компилятор и аппаратное обеспечение АО: кэш, процессор) будем это делать! ▪ А вы (программисты), в свою очередь, должны обеспечить возможность корректной трансформации и выполнения программы так, чтобы сохранялась иллюзия последовательной согласованности, включив в свою программу необходимые ограничения. 47
  • 53.
    ˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) 48
  • 54.
    ˝˿˵˶˼̍̀˱˽̐̃˹̎̃˿˵˿˴˿˳˿́ Вы обещаете Корректно реализовать синхронизацию в вашей программе (путём добавления необходимых инструкций в программу, делающих её безопасной относительно гонок) Система обещает Обеспечить иллюзию выполнения той программы, которую вы написали. (путём компиляции и выполнения) Модель памяти определяет, какие действия вы должны совершить и как должна отреагировать система, чтобы обеспечить выполнение операций с памятью в необходимой последовательности. 49
  • 55.
  • 56.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Алгоритм Деккерапозволяет решать проблему взаимного исключения и был опубликован в 1965 г. Он не приводит к взаимным исключениям (deadlock) и свободен от голодания (starvation). Поток 1 flag1 = 1; if (flag2 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию Поток 2 flag2 = 1; if (flag1 != 0) // ожидать освобождения // критической секции else // войти в критическую // секцию 51
  • 57.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag2 = 1; if (flag1 != 0) { … } flag1 = 1; if (flag2 != 0) { … } Store Buffer Store Buffer Память: flag1 = 0, flag2 = 0 52
  • 58.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Store Buffer Сохранение 1 в буфере 53
  • 59.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 54
  • 60.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 StoreLoad 55
  • 61.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 0, flag2 = 0 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 56
  • 62.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˾˱̀́˹˽˶́˶˱˼˴˿́˹̃˽˱˕˶˻˻˶́˱ Процессор 1Процессор 2 flag1 = 1; if (flag2 != 0) { … } Память: flag1 = 1, flag2 = 1 flag2 = 1; if (flag1 != 0) { … } Store Buffer flag1 = 1 Сохранение 1 в буфере Сохранение 1 в буфере Store Buffer flag1 = 1 Чтение 0 для flag2 Чтение 0 для flag1 Сброс буфера (flag1) Сброс буфера (flag2) 57
  • 63.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ ˣ˹̀ ̀˶́˶̄̀˿́̐ ˵˿̈˹˳˱˾˹̐ ˱́̆˹̃˶˻̃̄́̌ $OSKD $50Y 32:(5 63$5 502 63$5 362 63$5 762 [ $0' ,$ /RDGV̀˿̂˼˶ ORDGV /RDGV̀˿̂˼˶ VWRUHV 6WRUHV̀˿̂˼˶ VWRUHV 6WRUHV̀˿̂˼˶ ORDGV ˑ˟̂ORDGV ˑ˟̂VWRUHV ˘˱˳˹̂˹˽̌˶ ORDGV АО - атомарные операции 58
  • 64.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ более сильноеупорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 59
  • 65.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳́˱˸˼˹̈˾̌̆̀́˿̇˶̂̂˿́˱̆ более сильноеупорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac При сильном упорядочивании (сильная модель памяти) каждая инструкция неявно реализует семантику захвата и освобождения. При слабом упорядочивании (слабая модель памяти) не накладывается никаких ограничений на порядок выполнения инструкций. 60
  • 66.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ более сильноеупорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 61
  • 67.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Intel ArchitecturesSoftware Developer’s Manual, Vol. 3: ▪ Операции чтения не могут быть переупорядочены с другими операциями чтения ▪ Операции записи не могут быть переупорядочены с другими операциями записями ▪ Операции записи не могут быть переупорядочены с другими операциями записи, кроме следующих исключений: … ▪ Операции чтения могут быть переупорядочены с более старыми операциями записи в другие области памяти, но не с операциями записи в ту же область. ▪ Операции чтения не могут перейти раньше инструкций LFENCE, MFENCE, операции записи - раньше инструкций LFENCE, SFENCE, MFENCE. ▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше операций чтения, записи, того или другого соответственно. 62
  • 68.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ x =0, y = 0 Процессор 1 mov x, 1 ; запись (load) ; 1 в x mov r1, y ; загрузка (store) ; из y в регистр Процессор 2 mov y, 1 ; запись (load) ; 1 в x mov r2, x ; загрузка (store) ; из y в регистр Возможные варианты: ▪ r1 = 0, r2 = 1 ▪ r1 = 1, r2 = 0 ▪ r1 = 1, r2 = 1 ▪ r1 = 0, r2 = 0 63
  • 69.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ x =0, y = 0 Процессор 1 mov x, 1 ; запись (load) ; 1 в x StoreLoad mov r1, y ; загрузка (store) ; из y в регистр Процессор 2 mov y, 1 ; запись (load) ; 1 в x mov r2, x ; загрузка (store) ; из y в регистр ▪ r1 = 0, r2 = 0 В процессорах архитектуры x86 достустима перестановка операция load после store. Или, то же, операции store могут выполняться до операций load. 64
  • 70.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Программный барьер памяти: запретить переупорядочивание инструкций компилятором. 65
  • 71.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile( ::: printf(reordering happened!n); memory); r2 = x; 66 Переупорядочивание может произойти!
  • 72.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile( ::: printf(reordering happened!n); memory); r2 = x; 67
  • 73.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r1 = y; Поток 2 y = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r2 = x; Поток 3 if (r1 == 0 r2 == 0) printf(reordering happened!n); 68
  • 74.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r1 = y; Аппаратный барьер памяти: запретить переупорядочивание инструкций процессором. Программный барьер памяти: запретить переупорядочивание инструкций компилятором. 69 mov DWORD PTR X[rip], 1 mfence mov eax, DWORD PTR Y[rip] ... mov DWORD PTR r1[rip], eax полный барьер памяти
  • 75.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌[ Поток 1 x = 1; asm volatile(mfence ::: memory); asm volatile( ::: memory); r1 = y; Поток 3 if (r1 == 0 r2 == 0) Поток 2 y = 1; asm volatile(mfence ::: asm volatile( ::: r2 = x; printf(reordering happened!n); memory); memory); Переупорядочивание не произойдёт! 70
  • 76.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ более сильноеупорядочивание (strong model, sequential consistency) IBM Blue Gene Смартфоны DEC Alpha Xbox 360 PowerPC ARM x86 / 64 SPARC TSO более слабое упорядочивание (weak model, relaxed ordering) Как правило сильное упорядочивание Ослабленное упорядочивание с поддержкой зависимостей по данным Вполне ослабленное упорядочивание PowerMac 71
  • 77.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленногоупорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса flag.store(0, std::memory_order_relaxed)) count++; } } 72
  • 78.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленногоупорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса flag.store(0, std::memory_order_relaxed)) count++; } } StoreStore 73
  • 79.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Для ослабленногоупорядочивания характерно то, что одно ядро может видеть изменения в общей памяти в порядке, отличном от порядка, в котором другое ядро вносит изменения. int sharedCount; // глобальный счётчик void IncShared() { int count = 0; // локальный счётчик while (count N) { randomBusyWork(); // случайная задержка // наивная реализация мьютекса int expected = 0; if (flag.compare_exchange_strong(expected, 1, std::memory_order_relaxed)); { sharedVal++; // выполняется под защитой мьютекса asm volatile( ::: memory); flag.store(0, std::memory_order_relaxed)) count++; } } StoreStore запрет компиляторного переупорядочивания 74
  • 80.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 1 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 75
  • 81.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 1 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 76
  • 82.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Поток 1 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { (2) sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } } Поток 2 int sharedCount; void IncShared() { int count = 0; while (count N) { randomBusyWork(); int expected = 0; if (...) { (3) sharedVal++; asm volatile(...); flag.store(0, ...) count++; } } 77
  • 83.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˳̀́˿̇˶̂̂˿́˱̆̂˿̂˼˱˲˼˶˾˾̌˽̄̀˿́̐˵˿̈˹˳˱˾˹˶˽ Допустим N= 100000 Каждый из 2 потоков выполняет 100000 операций инкремента разделяемой переменной sharedCount++. Ожидаемое значение: sharedVal = 200000 В реальности: $ ./prog 100000 // N = 100000 sharedCount = 199348 sharedCount = 199034 sharedCount = 199517 sharedCount = 199829 sharedCount = 199113 sharedCount = 199566 78
  • 84.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 79
  • 85.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 80
  • 86.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 81
  • 87.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } Буфер 1 Буфер 2 Буфер N L2-кэш ... ... 82
  • 88.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } ... ... L2-кэш: widget[i].y = y; widget[i].ready = true; 83
  • 89.
    ˠ˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶̀́˹˳̌̀˿˼˾˶˾˹˹˿̀˶́˱̇˹̐˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐˳ ̀́˿̇˶̂̂˿́˱̆˱́̆˹̃˶˻̃̄́̌32:(5 Поток1 (процессор 1) // Подготавливаем данные // и устанавливаем StoreStore флаг // готовности for (i = 0; i n; i++) { widget[i].x = x; widget[i].y = y; widget[i].ready = true; } Поток 2 (процессор 2) // Если данные готовы, // обрабатываем их for (i = 0; i n; i++) { if (widget[i].ready) { do_some_stuff(widget[i]); } } ... ... L2-кэш: widget[i].y = y; widget[i].ready = true; 84
  • 90.
  • 91.
    ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ Первое правилоробототехники компиляторов и процессоров при упорядочивании доступа к памяти: Нельзя изменять поведение однопоточной программы. ▪ В однопоточных программах переупорядочивания остаются незамеченными. ▪ То же самое - при многопоточном программировании на основе мьютексов, семафоров и т.д. ▪ Но не при использовании атомарных переменных и техник программирования без блокировок. 86
  • 92.
    ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x,y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); mov eax, DWORD PTR y[rip] add eax, 111 mov DWORD PTR x[rip], eax mov DWORD PTR y[rip], 222 gcc -S -masm=intel prog.c выполнение команды x = y + 111 завершено выполнение команды y = 222 завершено 87
  • 93.
    ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x,y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi gcc -S -masm=intel -O2 prog.c y = 222 выполняется раньше x = y + 111 выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено 88
  • 94.
    ˠ́˿˴́˱˽˽˾˿˶̀˶́˶̄̀˿́̐˵˿̈˹˳˱˾˹˶˹˾̂̃́̄˻̇˹˺ int x,y; int main() { x = y + 111; asm volatile( ::: memory); y = 222; printf(%d%d, x, y); mov eax, DWORD PTR y[rip] add eax, 111 mov DWORD PTR x[rip], eax mov esi, DWORD PTR x[rip] mov edx, 222 ... xor eax, eax mov DWORD PTR y[rip], 222 явный барьер gcc -S -masm=intel -O2 prog.c 89
  • 95.
  • 96.
    int data; boolisReleased = false; // данные опубликованы? void releaseData(int val) // опубликовать данные { data = val; // записать данные isReleased = true; // данные опубликованы, } // можно с ними работать 90
  • 97.
  • 98.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Данные должны быть проинициализированы в 1 потоке перед тем, как они будут использованы во 2 потоке. 91
  • 99.
  • 100.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проиницаилизрованы 92
  • 101.
  • 102.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 1. Данные проициализированы 2. Ура! Второй поток может обрабатывать 93
  • 103.
  • 104.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 94
  • 105.
  • 106.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 95
  • 107.
  • 108.
    Поток 1 StoreStore int data; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 96
  • 109.
  • 110.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы. 97
  • 111.
  • 112.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); doSomething(data); } 98
  • 113.
  • 114.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; asm volatile( ::: memory); isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); asm volatile( ::: memory); doSomething(data); } 99
  • 115.
  • 116.
    Поток 1 intdata; bool isReleased = false; void releaseData(int val) { data = val; asm volatile( ::: memory); isReleased = true; } Поток 2 void utilizeData() { while (!isReleased); asm volatile( ::: memory); doSomething(data); } Операция записи- освобождения Операция чтения- захвата 100
  • 117.
  • 118.
    if (x 0) y++; register int r = y; if (x 0) r++; y = r; Размещение переменной в регистре в процессе оптимизирующей компиляции создание новой регистровой переменной Новая операция сохранения (store) “из чистого воздуха” Создание операций сохранения “из чистого воздуха” не допустимо в соответствии с последним стандартом, однако... 101
  • 119.
  • 120.
    однако в С PThreads такая оптимизация допускается: static pthread_mutex_t lock = PTHREAD_MUTEX_INITALIZER; static int count = 0; int trylock() { int rc; rc = pthread_mutex_trylock(mutex); if (rc == 0) count++; ... int trylock() { register int r = count; int rc; rc = pthread_mutex_trylock(mutex); if (rc == 0) count++; count = r; новый store и новая гонка данных! 102
  • 121.
  • 122.
  • 123.
  • 124.
    Отношение happens-before определяет,какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; // A y = x + 1; // B 104
  • 125.
  • 126.
    Отношение happens-before определяет,какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; y = x + 1; z = sqrt(x * y); Все соотношения happens-before для операции А (x = 10) 105
  • 127.
  • 128.
    Отношение happens-before определяет,какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) print(x) 106
  • 129.
  • 130.
    Отношение happens-before определяет,какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. Поток 1 Поток 2 x = 10; y = x + 1; z = sqrt(x * y); m = k - 5; print(m) w = x + 2 107
  • 131.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ Отношение happens-beforeопределяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В 108
  • 132.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Изтого, что А происходит-раньше В не следует, что А происходит раньше В. int x, y; int main() { x = y + 111; // A y = 222; // B printf(%d%d, x, y); 109
  • 133.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Изтого, что А происходит-раньше В не следует, что А int x, y; int main() { x = y + 111; // A y = 222; // B printf(%d%d, x, y); mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi gcc -S -masm=intel -O2 prog.c происходит раньше В. 110
  • 134.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 1. Изтого, что А происходит-раньше В не следует, что А int x, y; int main() { x = y + 111; y = 222; printf(%d%d, x, y); mov eax, DWORD PTR y[rip] mov edx, 222 ... mov DWORD PTR y[rip], 222 lea esi, [rax+111] ... mov DWORD PTR x[rip], esi gcc -S -masm=intel -O2 prog.c y = 222 выполняется раньше x = y + 111 выполнение команды y = 222 завершено выполнение команды x = y + 111 завершено происходит раньше В. 111
  • 135.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Изтого, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; // A 112
  • 136.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Изтого, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) // B print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; // A 113
  • 137.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Изтого, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; 114
  • 138.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶˾˶˿˸˾˱̈˱˶̃̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ 2. Изтого, что А происходит раньше В не следует, что А происходит-раньше В Поток 2 if (ready) print(x); int x; bool ready = false; Поток 1 x = 42 ready = true; 115
  • 139.
    ˠ́˿˹̂̆˿˵˹̃́˱˾̍̉˶Ƨ̀́˿˹̂̆˿˵˹̃́˱˾̍̉˶ Отношение happens-beforeопределяет, какие операции видят последствия других операций. Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В. 1. Из того, что А происходит-раньше В не следует, что А происходит раньше В. 2. Из того, что А происходит раньше В не следует, что А происходит-раньше В Отношение происходит-раньше имеет место тогда (и только тогда), когда это определёно стандартом языка. 116
  • 140.
  • 141.
  • 142.
    std::atomicint x{0}, y{0},v{0}, w{0} void thread1() { x.store(1, std::memory_order_seq_cst); v.store(y.load(std::memory_order_seq_cst), std::memory_order_seq_cst); } void thread2() { y.store(1, std::memory_order_seq_cst); w.store(x.load(std::memory_order_seq_cst), std::memory_order_seq_cst); } int main() { std::thread t1{thread1}, t2{thread2}; t1.join(); t2.join(); assert(v != 0 || w != 0); assert не сработает 118
  • 143.
  • 144.
    std::atomicint data{0}; std::atomicboolready{false}; int main() { std::thread producer{[]{ data.store(42, std::memory_order_seq_cst); ready.store(true, std::memory_order_seq_cst); }}; std::thread consumer{[]{ while (!ready.load(std::memory_order_seq_cst)) { } std::cout data.load(std::memory_order_seq_cst); }}; producer.join(); consumer.join(); 42 119
  • 145.
  • 146.
    std::atomicint z{0}; std::atomicboolx{false}, y{false}; int main(int argc, const char *argv[]) { std::thread store_y{[]{ x.store(true, std::memory_order_seq_cst); }}; std::thread store_x{[]{ y.store(true, std::memory_order_seq_cst); }}; std::thread read_x_then_y{[]{ while (!x.load(std::memory_order_seq_cst)) { } if (y.load(std::memory_order_seq_cst)) z.fetch_add(1, std::memory_order_seq_cst); }}; std::thread read_y_then_x{[]{ while (!y.load(std::memory_order_seq_cst)) { } if (y.load(std::memory_order_seq_cst)) z.fetch_add(1, std::memory_order_seq_cst); }}; store_x.join(); store_y.join(); read_x_then_y.join(); read_y_then_x.join(); std::cout z.load(std::memory_order_seq_cst); z 0 120
  • 147.
  • 148.
    std::atomicint x{0}, y{0},v{0}, w{0} void thread1() { x.store(1, std::memory_order_relaxed); v.store(y.load(std::memory_order_relaxed), std::memory_order_relaxed); } void thread2() { y.store(1, std::memory_order_relaxed); w.store(x.load(std::memory_order_relaxed), std::memory_order_relaxed); } int main() { std::thread t1{thread1}, t2{thread2}; t1.join(); t2.join(); assert(v != 0 || w != 0); assert может сработать 121
  • 149.
  • 150.
    std::atomicint data{0}; std::atomicboolready{false}; int main() { std::thread producer{[]{ data.store(42, std::memory_order_relaxed); ready.store(true, std::memory_order_relaxed); }}; std::thread consumer{[]{ while (!ready.load(std::memory_order_relaxed)) { } std::cout data.load(std::memory_order_relaxed); }}; producer.join(); consumer.join(); 42 не гарантируется 122
  • 151.
  • 152.
    std::atomicint z{0}; std::atomicboolx{false}, y{false}; int main(int argc, const char *argv[]) { std::thread store_y{[]{ x.store(true, std::memory_order_relaxed); }}; std::thread store_x{[]{ y.store(true, std::memory_order_relaxed); }}; std::thread read_x_then_y{[]{ while (!x.load(std::memory_order_relaxed)) { } if (y.load(std::memory_order_relaxed)) z.fetch_add(1, std::memory_order_relaxed); }}; std::thread read_y_then_x{[]{ while (!y.load(std::memory_order_relaxed)) { } if (y.load(std::memory_order_relaxed)) z.fetch_add(1, std::memory_order_relaxed); }}; store_x.join(); store_y.join(); read_x_then_y.join(); read_y_then_x.join(); std::cout z.load(std::memory_order_relaxed); z = 0 123
  • 153.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x x.load (memory_order_relaxed) 124
  • 154.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x 125 x.load (memory_order_relaxed)
  • 155.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Иван Значение переменной x 126 x.load (memory_order_relaxed) encore!
  • 156.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 Значение переменной x x.load (memory_order_relaxed) Иван 127
  • 157.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 -8 Иван Значение переменной x x.store(-8, memory_order_relaxed) 128
  • 158.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван 34 9 45 -3 21 097 11 -8 Значение переменной x x.load (memory_order_relaxed) Иван 129
  • 159.
    ˝˶̃˱̅˿́˱̈˶˼˿˳˶˻˱̂˲˼˿˻˾˿̃˿˽ Иван Сергей Анна ... 34 9 45 -3 21 097 11 -8 Иван Анна Сергей Значение переменной x 130
  • 160.
  • 161.
  • 162.
    ˙˶́˱́̆˹̈˶̂˻˱̐̂̃́̄˻̃̄́˱̂˿˳́˶˽˶˾˾̌̆˽˾˿˴˿̐˵˶́˾̌̆̂˹̂̃˶˽ кэш 2 кэш 1 Память кэш 2 кэш 1 кэш 1 кэш 1 Процессорные ядра 133
  • 163.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 кэшL1 2 кэш L1 Память 134
  • 164.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 кэшL1 2 кэш L1 Память mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] 135
  • 165.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Центральный репозиторий 136
  • 166.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий 137
  • 167.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 0 x = 0 138
  • 168.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 1 x = 0 139
  • 169.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Утечка данных из центрального репозитория в локальный и обратно Центральный репозиторий mov [x], 1 x = 1 x = 1 x = 1 140
  • 170.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 Рекс Мухтар Центральный репозиторий mov [x], 1 x = 1 x = ? x = ? Неизвестно, когда изменения распространятся на другие потоки. 141
  • 171.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 0 y = 0 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 0 x = 0 y = 0 142
  • 172.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 1 y = 0 Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = 0 y = 1 x = 0 y = 0 143
  • 173.
    ˝˶̃˱̅˿́˱́˶̀˿˸˹̃˿́˹̐ 1 2 x = 1 y = ? {0,1} Рекс Мухтар Центральный репозиторий mov [x], 1 mov r1, [y] mov [y], 1 mov r2, [x] x = ? {0,1} y = 1 x = ? {0,1} y = ? {0,1} 144
  • 174.
  • 175.
    ˒˱́̍˶́/RDG/RDG 1 Рекс Центральный репозиторий LoadLoad x = 1 y = 0 x = 1 y = 0 git pull, hg pull, svn update, cvs update Барьер LoadLoad: ▪ Предотвращает переупорядочивания между загрузками до барьера и загрузками после барьера. ▪ Гарантирует, что загруженные из центрального репозитория (памяти) в локальный репозиторий (кэш) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из центрального репозитория. 146
  • 176.
    ˒˱́̍˶́/RDG/RDG 1 Рекс Центральный репозиторий LoadLoad x = 1 y = 0 x = 1 y = 0 git pull, hg pull, svn update, cvs update // Так получилось, что // widget.ready = true утекло // в локальный репозиторий if (widget.ready) { // Подгрузить всё остальное // содержимое widget - // такое же “свежее”, как ready LoadLoadFence(); do_something(widget); } 147
  • 177.
    ˒˱́̍˶́6WRUH6WRUH 1 Рекс Центральный репозиторий StoreStore x = 1 y = 0 Барьер StoreStore: ▪ Предотвращает переупорядочивания между сохранениями до барьера и сохранениями после барьера. ▪ Гарантирует, что загруженные из локального репозитория (кэш) в локальный репозиторий (память) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из локального репозитория. x = 1 y = 0 git push, hg push, svn commit, cvs commit 148
  • 178.
    ˒˱́̍˶́6WRUH6WRUH 1 Рекс Центральный репозиторий StoreStore x = 1 y = 0 x = 1 y = 0 git push, hg push, svn commit, cvs commit widget.x = x; widget.y = y; StoreStoreFence(); widget.ready = true; // Если ready = true просочится // Мухтару, то он увидит увидит // на центральном репозитории // всё остальные поля widget, // которые для него подготовил Рекс 149
  • 179.
    ˒˱́̍˶́/RDG6WRUH 1 movr1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Операции загрузки load Операции сохранения store Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 150
  • 180.
    ˒˱́̍˶́/RDG6WRUH 1 movr1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Операции загрузки load - отложены Операции сохранения store - выполнены Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, то он просматривает следующие операции сохранения, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 151
  • 181.
    ˒˱́̍˶́/RDG6WRUH 1 movr1, [y] mov r2, [x] mov [z], 42 mov [w], r3 mov [v], r4 Рекс Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 152
  • 182.
    ˒˱́̍˶́/RDG6WRUH 1 movr1, [y] mov r2, [x] LoadStore mov [z], 42 mov [w], r3 mov [v], r4 Рекс Будут промахи по кэшу... Будут попадания в кэш... Переупорядочивание LoadStore 1. Есть набор инструкций, состоящий из операций сохранения и загрузки. 2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 153
  • 183.
    ˒˱́̍˶́6WRUH/RDG 1 mov[x], 1 mov r1, [y] x = 1 y = 0 Рекс 2 mov [y], 1 mov r2, [x] x = 0 y = 1 Мухтар r1 = 0 r2 = 0 154
  • 184.
    ˒˱́̍˶́6WRUH/RDG 1 mov[x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс 2 mov [y], 1 StoreLoad mov r2, [x] x = ? y = 1 Мухтар Барьер StoreLoad: ▪ Гарантирует видимость для других процессоров всех операций сохранения, выполненных до барьера. ▪ Обеспечивает для всех операций загрузки, выполненных после барьера, получение результатов, которые имеют место во время барьера. ▪ Барьер предотвращает r1 = r2 = 0 ▪ StoreLoad ≠ StoreStore + LoadLoad 155
  • 185.
    ˒˱́̍˶́6WRUH/RDG 1 mov[x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 156
  • 186.
    ˒˱́̍˶́6WRUH/RDG 1 mov[x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). 157
  • 187.
    ˒˱́̍˶́6WRUH/RDG 1 mov[x], 1 StoreLoad mov r1, [y] x = 1 y = ? Рекс Центральный репозиторий Барьер StoreLoad (≠ StoreStore + LoadLoad): 1. Отправка (push) всех изменений в центральный репозиторий. 2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой). 3. Загрузка (pull) всех последних изменений из центрального репозитория (в отличие от LoadLoad, который не загружает абсолютно последние изменения) 158
  • 188.
  • 189.
  • 190.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ Семантика захвата(acquire) ▪ Применяется к операциям чтения или чтения- модификации-записи, при этом такая операция становится операцией чтения-захвата (read-acquire). ▪ Предотвращает переупорядочивание инструкции чтения- захвата и всех следующих в программе операций чтения или записи. ▪ Применяется к операциям записи или чтения- модификации-записи, причём такая операция становится операцией записи-освобождения (write-release). ▪ Предотвращает переупорядочивание инструкции записи- освобождения со всеми предшествующими в программе операциями чтения или записи. Семантика освобождения (release) 160
  • 191.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ read-acquire read/ write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 161
  • 192.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˹˿̂˳˿˲˿˷˵˶˾˹̐ read-acquire read/ write read / write read / write read / write read / write read / write read / write read / write read / write read / write write-release 162
  • 193.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ Acquire (захват) LoadLoadFence LoadStoreFence StoreLoadFence StoreStoreFence Release (освобождение) 163
  • 194.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 164
  • 195.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 165
  • 196.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 166
  • 197.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 1 2 167
  • 198.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write StoreStoreFence + LoadStoreFence write-release 2 1 1 2 168
  • 199.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳̃˶́˽˹˾˱̆˲˱́̍˶́˿˳ read-acquire LoadLoadFence+ LoadStoreFence read / write read / write read / write read / write read / write read / write read / write read / write LoadStoreFence + StoreStoreFence write-release 169
  • 200.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Поток 1 x = 42 ready.store(true, memory_order_release); 170
  • 201.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 1 x = 42 ready.store(true, memory_order_release); Synchronizes-with Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с Всё, что здесь... … будет видно здесь 171
  • 202.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 1 x = 42 ready.store(true, memory_order_release); Synchronizes-with Поток 2 ready.load( memory_order_acquire); print(x); int x; bool ready = false; Синхронизируется-с … будет видно здесь Всё, что здесь... Операция записи- освобождения Операция чтения- захвата 172
  • 203.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Поток 1 void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release); } Поток 2 void useWidget() { while (!ready.load( memory_order_acquire)) {} doSomething(w); } widget w; bool ready = false; 173
  • 204.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток2 void useWidget() { while (!ready.load( memory_order_acquire)) {} doSomething(w); } widget w; bool ready = false; Поток 1 void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release); } Синхронизируется-с Всё, что здесь... … будет видно здесь Операция записи- освобождения Операция чтения-захвата 174
  • 205.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток2 void useWidget() { while (!ready.load( memory_order_acquire)) {} s1 = to_string(w); s2 = number + s1; print(s2); } int x, y, z; bool ready = false; Поток 1 void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release); } Синхронизируется-с 175
  • 206.
    ˢ˶˽˱˾̃˹˻˱˸˱̆˳˱̃˿̂˳˿˲˿˷˵˶˾˹˶˾˱̀́˹˽˶́˶̂̀̄˲˼˹˻˱̇˹˶˺ Synchronizes-with Поток2 void useWidget() { while (!ready.load( memory_order_acquire)) {} s1 = to_string(w); s2 = number + s1; print(s2); } int x, y, z; bool ready = false; Поток 1 void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release); } Синхронизируется-с Операция записи- освобождения Операция чтения-захвата 176
  • 207.
    ˘˱̆˳˱̃˹˿̂˳˿˲˿˷˵˶˾˹˶˳ˢ std::memory_order_acquire ▪Применяется к операциям чтения или чтения- модификации-записи: load, compare_exchange, fetch_add, fetch_or, etc. ▫ x.load(std::memory_order_acquire); ▫ compare_exchange_weak(x, y, std::memory_order_acquire); ▪ Применяется к операциям записи или чтения- модификации-записи: store, operator=, load, compare_exchange, fetch_add, fetch_or, etc ▫ x.store(42, std::memory_order_release); ▫ compare_exchange_weak(x, y, std::memory_order_release); std::memory_order_release 177
  • 208.
  • 209.
  • 210.
  • 211.
    Отношение syncrhonized-with болеесильное по сравнению с happens-before, т.е.: synchronizes-with ⟶ happens-before 179
  • 212.
  • 213.
    Отношение syncrhonized-with болеесильное по сравнению с happens-before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 180
  • 214.
  • 215.
    Отношение syncrhonized-with болеесильное по сравнению с happens-before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 181
  • 216.
  • 217.
    Отношение syncrhonized-with болеесильное по сравнению с happens-before, т.е.: synchronizes-with ⟶ happens-before Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. Иначе: Если поток 1 сохраняет значение, а поток 2 читает это значение, то существует отношение синхронизируется-с между операциями сохранения и загрузки. 182
  • 218.
  • 219.
    ˳ˢ Поток 1 void prepare() { w.x = x; w.y = x; cntr++; w.ready.store(true, memory_order_release); } Synchronizes-with Поток 2 void utilize() { while (!w.ready.load( memory_order_acquire)) {} doSomethingWith(w); writeLog(cntr); } Widget w; int cntr; Синхронизируется-с 183
  • 220.
  • 221.
    ˳ˢ Поток 1 void prepare(Widget w) { w.x = x; w.y = x; cntr++; w.ready.store(true, memory_order_release); } Synchronizes-with Поток 2 void utilize() { if (!w.ready.load( memory_order_acquire)){ doSomethingWith(w); writeLog(cntr); } Widget w; int cntr; Синхронизируется-с 184
  • 222.
  • 223.
    ˳ˢ Поток 2 void utilize() { bool ready = w.ready.load( memory_order_relaxed); if (ready) { atomic_thread_fence( memory_order_acquire)){ doSomethingWith(w); writeLog(cntr); } Widget w; int cntr; Поток 1 void prepare(Widget w) { w.x = x; w.y = x; cntr++; atomic_thread_fence(, memory_order_release); w.ready.store(true, memory_order_relaxed); } 185
  • 224.
  • 225.
    ˳ˢ Рекс Мухтар Центральный репозиторий (ЦР) 186
  • 226.
  • 227.
    ˳ˢ Рекс Мухтар Центральный репозиторий (ЦР) Запись в w, cntr 1 187
  • 228.
  • 229.
    ˳ˢ Запись вw, загружаются в “репозиторий” cntr Рекс Мухтар Барьер release - все данные Центральный репозиторий (ЦР) 1 2 188
  • 230.
  • 231.
    ˳ˢ Запись вw, загружаются в “репозиторий” cntr Рекс Мухтар Барьер release - все данные Центральный репозиторий (ЦР) 1 2 Запись true в ready 3 189
  • 232.
  • 233.
    ˳ˢ Запись true в ready Рекс Барьер release - все данные загружаются в “репозиторий” Мухтар Центральный репозиторий (ЦР) Запись в w, cntr 1 2 ready просачивается в “репозиторий” 3 4 190
  • 234.
  • 235.
    ˳ˢ 2 1Барьер release - все данные Рекс Мухтар загружаются в “репозиторий” Центральный репозиторий (ЦР) Запись в w, cntr ready просачивается в “репозиторий” ready просачивается из ЦР Мухтару Запись true в ready 3 4 5 191
  • 236.
  • 237.
    ˳ˢ 1 Барьерrelease - все данные Рекс 6 5 Мухтар Центральный репозиторий (ЦР) Запись в w, cntr загружаются в “репозиторий” 2 Запись true в ready 3 ready просачивается в “репозиторий” ready просачивается из ЦР Мухтару 4 Чтение true в ready 192
  • 238.
  • 239.
    ˳ˢ Запись вw, загружаются в “репозиторий” cntr Рекс Мухтар Барьер release - все данные Центральный репозиторий (ЦР) 1 2 ready просачивается в “репозиторий” ready просачивается из ЦР Мухтару Барьер acquire - данные загружаются из ЦР Мухатору Запись true в ready 3 4 5 7 Чтение true в ready 6 193
  • 240.
  • 241.
    ˳ˢ Рекс Мухтар Центральный репозиторий (ЦР) Запись в w, cntr Барьер release - все данные загружаются в “репозиторий” ready просачивается в “репозиторий” 1 2 Запись true в ready 3 4 5 ready просачивается из ЦР Мухтару Чтение true в ready Барьер acquire - данные загружаются из ЦР Мухатору Чтение w и 7 cntr 6 8 194
  • 242.
    ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˿̂̃̍˿̂˳˿˲˿˷˵˶˾˹˺ std::vectorint queue; std::atomicint count; void pop() { for (auto i = 0; i num_of_items; i++) queue.push(i); count.store(num_of_items, std::memory_order_release); } void consume() { for (;;) { auto item_index = count.fetch_sub(1, ?); if (item_index = 0) { wait_for_items(); continue; } process(queue[item_index]); } } 195
  • 243.
  • 244.
    Операция записи Aнад переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 196
  • 245.
  • 246.
    Операция записи Aнад переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое 1. или операцией A. 2. или следующей за A операцией записи над x в том же потоке, который выполнил A. 3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A. 197 Если существует последовательность операций чтения-модификации- записи, помеченных признаком acquire, причём каждая операция загрузки загружает значение, записанное предыдущей операцией, то такая цепочка называется последовательностью освобождений. Операция чтения может быть помечена любым признаком.
  • 247.
    ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˿̂̃̍˿̂˳˿˲˿˷˵˶˾˹˺ std::vectorint queue; std::atomicint count; void pop() { for (auto i = 0; i num_of_items; i++) queue.push(i); count.store(num_of_items, std::memory_order_release); } void consume() { for (;;) { auto item_index = count.fetch_sub(1, std::memory_order_acquire); if (item_index = 0) { wait_for_items(); continue; } process(queue[item_index]); } } 198
  • 248.
    ˠ˿̂˼˶˵˿˳˱̃˶˼̍˾˿̂̃̍˿̂˳˿˲˿˷˵˶˾˹˺ 199 T1T2 T3 pop() { for (...) { queue.push (...) } count.store( release) consume() { ... count.fetch_sub ( acquire) consume() { process(...) ... count.fetch_sub( acquire) process(...) 10 9 8 синхронизируется-с цепочка зависимостей происходит раньше
  • 249.
  • 250.
    ˡ˶˱˼˹˸˱̇˹̐̂̀˹˾˼˿˻˱˾˱˿̂˾˿˳˶˱̃˿˽˱́˾˿˴˿̅˼˱˴˱ class spinlock_mutex { std::atomic_flag flag; public: spinlock_mutex(): flag{ATOMIC_FLAG_INIT} { } void lock() { while (flag.test_and_set( std::memory_order_acquire)); } void unlock() { flag.clear(std::memory_order_release); } }; 201
  • 251.
    ˡ˶˱˼˹˸˱̇˹̐̂̀˹˾˼˿˻˱˾˱˿̂˾˿˳˶˱̃˿˽˱́˾˿˴˿̅˼˱˴˱ class spinlock_mutex { std::atomic_flag flag; public: spinlock_mutex(): flag{ATOMIC_FLAG_INIT} { } void lock() { while (flag.test_and_set( std::memory_order_acquire)); } void unlock() { flag.clear(std::memory_order_release); } }; Операция записи-освобождения Операция чтения-захвата 202
  • 252.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Что всё-такиподразумевается под переупорядочиванием инструкций (A и В) в потоке? T1 ... mut.lock(); // A y = 2; // B mut.unlock(); ... Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке. 203
  • 253.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Что всё-такиподразумевается под переупорядочиванием инструкций (A и В) в потоке? T1 T2 ... mut.lock(); y = 2; mut.unlock(); ... 2 1 Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке. 204
  • 254.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Что всё-такиподразумевается под переупорядочиванием инструкций (A и В) в потоке? Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке. T1 T2 ... mut.lock(); y *= 10; mut.unlock(); ... ... mut.lock(); y = 2; mut.unlock(); ... 205
  • 255.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Что всё-такиподразумевается под переупорядочиванием инструкций (A и В) в потоке? Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке. T1 T2 ... mut.lock(); mut.unlock(); y = 2; ... ... mut.lock(); y *= 10; mut.unlock(); ... гонка! 206
  • 256.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Можно трансформироватьэто в это? x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); 207
  • 257.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Можно трансформироватьэто в это? x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); а это в это? mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; 208
  • 258.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Можно трансформироватьэто в это? x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); а это в это? mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); x = 1; // гонка! mut.lock(); y = 2; mut.unlock(); z = 3; // гонка! 209
  • 259.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Допустим ... void lock() { while (flag.test_and_set( std::memory_order_relaxed)); } void unlock() { flag.clear(std::memory_order_relaxed); } 210
  • 260.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ Допустим ... void lock() { while (flag.test_and_set( std::memory_order_relaxed)); } void unlock() { flag.clear(std::memory_order_relaxed); } Но тогда mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; 211
  • 261.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ ... voidlock() { while (flag.test_and_set( std::memory_order_relaxed)); } void unlock() { flag.clear(std::memory_order_relaxed); } Но тогда mut.lock(); x = 1; y = 2; z = 3; mut.unlock(); x = 1; mut.lock(); y = 2; mut.unlock(); z = 3; Допустим 212
  • 262.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ k =1; l = 2; mut.lock(); x = 3; y = 4; z = 5; mut.unlock(); p = 6; q = 7; 213
  • 263.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ flag.test_and_set( std:: memory_order_acquire) k = 1; l = 2; mut.lock(); x = 3; y = 4; z = 5; mut.unlock(); p = 6; q = 7; flag.clear(std:: memory_order_release) 214
  • 264.
    acquire release ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ flag.test_and_set( std:: memory_order_acquire) k = 1; l = 2; mut.lock(); x = 3; y = 4; z = 5; mut.unlock(); p = 6; q = 7; flag.clear(std:: memory_order_release) Мьютекс определяет семантику захвата-освобождения (lock - запись-освобождение, release - чтение-захват): mutex ⟶ acquire-release 215
  • 265.
    acquire release ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ flag.test_and_set( std:: memory_order_acquire) k = 1; l = 2; mut.lock(); x = 3; y = 4; z = 5; mut.unlock(); p = 6; q = 7; flag.clear(std:: memory_order_release) Мьютекс определяет семантику захвата-освобождения (lock - запись-освобождение, release - чтение-захват): mutex ⟶ acquire-release Является ли пара операций захвата-освобождения достаточной для реализации взаимного исключения acquire-release ⟶ mutex ?? 216
  • 266.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2T3 ... mut.lock(); y = 2; mut.unlock(); ... ... mut.lock(); y *= 10; mut.unlock(); ... ... mut.lock(); y += 22; mut.unlock(); ... 217
  • 267.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2T3 flag.load() // 0 ... y = 2; ... flag.store(0) ... flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 0 ... y += 22; ... flag.store(0) flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 0 ... y *= 10; ... flag.store(0); 218
  • 268.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2T3 load-acquire ... y = 2; ... store-release ... flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 load-acquire ... y += 22; ... store-release flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 flag.load() // 1 load-acquire ... y *= 10; ... store-release 219 Synchronizes-with Синхронизируется-с Synchronizes-with Синхронизируется-с
  • 269.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2T3 load-acquire ... y += 22; ... store-release load-acquire ... y *= 10; ... store-release Synchronizes-with Синхронизируется-с Synchronizes-with Синхронизируется-с 220 load-acquire ... y = 2; ... store-release ...
  • 270.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2T3 load-acquire ... y = 2; ... store-release ... load-acquire ... y += 22; ... store-release load-acquire ... y *= 10; ... store-release Synchronizes-with Синхронизируется-с Synchronizes-with Синхронизируется-с 221 Пара загрузки-сохранения (load-store) обеспечивает взаимное исключение в том и только в том случае, если загрузка является операцией захвата (acquire), а сохранение - операцией освобождения (release), и если операция загрузки синхронизируется-с (synchronized-with) операцией сохранения предыдущей пары загрузки-сохранения, а операция сохранения синхронизируется с операцией сохранения последующей пары загрузки сохранения.
  • 271.
  • 272.
    ˠ́˿˲˼˶˽˱˵˳˿˺˾˿˺̀́˿˳˶́˻˹ 223 Widget*Widget::instance_ptr{nullptr}; Widget* Widget::instance() { if (instance_ptr == nullptr) { // 1: 1 проверка lock_guardmutex lock{mut}; // 2: запираем if (instance_ptr == nullptr) { // 3: 2 проверка instance_ptr = new Widget(); // 4: создать // и присвоить } } // 5: отпираем return instance_ptr; // 6: возвращаем } Основная проблема: гонка данных между операциями 1 и 4. Она может привести, например, к частичной инициализации instance_ptr
  • 273.
    ˠ́˿˲˼˶˽˱˵˳˿˺˾˿˺̀́˿˳˶́˻˹ 224 std::atomicWidget*Widget::instance_ptr{nullptr}; Widget* Widget::instance() { if (instance_ptr == nullptr) { // 1: 1 проверка lock_guardmutex lock{mut}; // 2: запираем if (instance_ptr == nullptr) { // 3: 2 проверка instance_ptr = new Widget(); // 4: создать // и присвоить } } // 5: отпираем return instance_ptr; // 6: возвращаем }
  • 274.
    ˠ́˿˲˼˶˽˱˵˳˿˺˾˿˺̀́˿˳˶́˻˹ 225 std::atomicWidget*Widget::instance_ptr{nullptr}; Widget* Widget::instance() { if (instance_ptr == nullptr) { lock_guardmutex lock{mut}; if (instance_ptr == nullptr) { instance_ptr = new Widget(); } } return instance_ptr; } нужен барьер нужен барьер
  • 275.
    ˠ́˿˲˼˶˽˱˵˳˿˺˾˿˺̀́˿˳˶́˻˹˳ˢ 226 std::atomicWidget*Widget::instance_ptr{nullptr}; Widget* Widget::instance() { Widget* tmp = instance_ptr.load(memory_order_relaxed); atomic_thread_fence(memory_order_acquire); if (tmp == nullptr) { lock_guardmutex lock{mut}; tmp = instance_ptr.load(memory_order_relaxed); if (tmp == nullptr) { instance_ptr = new Widget(); atomic_thread_fence(memory_order_release); instance_ptr.store(tmp, memory_order_relaxed); } } return tmp; }
  • 276.
    ˠ́˿˲˼˶˽˱˵˳˿˺˾˿˺̀́˿˳˶́˻˹˳ˢ 227 std::atomicWidget*Widget::instance_ptr{nullptr}; Widget* Widget::instance() { if (instance_ptr.load(memory_order_acquire) == nullptr) { lock_guardmutex lock{mut}; tmp = instance_ptr.load(memory_order_relaxed); if (tmp == nullptr) { instance_ptr = new Widget(); instance_ptr.store(tmp, memory_order_release); } } return tmp; }
  • 277.
    ˝̍̏̃˶˻̂˹̂˶˽˱˾̃˹˻˱˸˱̆˳˱̃˱˿̂˳˿˲˿˷˵˶˾˹̐ T1 T2 instance_ptr = new Widget(); instance_ptr.store(tmp, memory_order_release); } if (instance_ptr.load( memory_order_acquire) == nullptr) { lock_guardmutex lock{mut}; tmp = instance_ptr.load( memory_order_relaxed); if (tmp == nullptr) { 228 всё здесь видно здесь Synchronizes-with Синхронизируется-с