ПВТ - осень 2014 - Лекция 6 - Атомарные операции....

228
Лекция 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата- освобождения. Модель памяти C++ Пазников Алексей Александрович Кафедра вычислительных систем СибГУТИ Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/ Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home Параллельные вычислительные технологии Осень 2014 (Parallel Computing Technologies, PCT 14)

Upload: alexey-paznikov

Post on 23-Jul-2015

593 views

Category:

Education


0 download

TRANSCRIPT

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

Пазников Алексей АлександровичКафедра вычислительных систем СибГУТИ

Сайт курса: http://cpct.sibsutis.ru/~apaznikov/teaching/Вопросы: https://piazza.com/sibsutis.ru/fall2014/pct14/home

Параллельные вычислительные технологииОсень 2014 (Parallel Computing Technologies, PCT 14)

Атомарные переменные

2

Атомарные операции

▪ Операция над разделяемой переменной атомарная, если она выполняется потоком за один неделимый шаг. Ни один из других потоков не может обнаружить эту переменную в промежуточном состоянии.

▪ Если операции, которые совершают потоки над раздялемыми переменными, не атомарны, то это приведёт к гонкам данных.

▪ Гонки данных являются причиной неопредлённого поведения, поскольку они приводят к частичным (фрагментированным, “разорванным”) чтениям и записям переменных.

3

Фрагментированные чтения и записи

mov DWORD PTR shared, 2

mov DWORD PTR shared+4, 1

uint64_t shared;

int main() {

shared = 0x100000002;

...

gcc -m32 -S -masm=intel -O1 prog.c

запись младших 32 битзапись старших 32 бит

▪ Выполнение присваивания 64-битного целого shared = 42 на 32-разрядной архитектуре выполняется за 2 инструкции.

▪ Операция записи не атомарная.4

Фрагментированные чтения и записи

mov DWORD PTR shared, 2

mov DWORD PTR shared+4, 1

uint64_t shared;

int main() {

shared = 0x100000002;

...

gcc -m32 -S -masm=intel -O1 prog.c

1 потокзапись старших 32 бит

▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти и будут использованы другими потоками.

▪ На многоядерных системах даже не требуется вытеснения. 5

Фрагментированные чтения и записи

mov DWORD PTR shared, 2

mov DWORD PTR shared+4, 1

uint64_t shared;

int main() {

shared = 0x100000002;

...

gcc -m32 -S -masm=intel -O1 prog.c

1 поток2 поток

▪ Вытеснение потока после записи младших бит приведёт к тому, что эти биты останутся в памяти, а старшие будут записаны другим потоком.

▪ На многоядерных системах даже не требуется вытеснения. 6

Фрагментированные чтения и записи

mov eax, DWORD PTR shared

mov edx, DWORD PTR shared+4

ret

uint64_t shared;

uint64_t getShared() {

return shared;

}

gcc -m32 -S -masm=intel -O1 prog.c

чтение младших 32 битчтение старших 32 бит

▪ Выполнение чтения 64-битного целого shared на 32-разрядной архитектуре выполняется за 2 инструкции.

▪ Операция чтения не атомарная.7

Фрагментированные чтения и записи

strd r0, r1, [r2]

▪ Инструкции могут быть неатомарными, даже если выполняются одной процессорной инструкцией.

Например, в ARMv7 инструкция для помещения содержимого двух 32-битных регистров в один 64-битный:

На некоторых процессорах эта инструкция реализуются двумя отдельными операциями сохранения. 32-битная операция mov атомарна только для выравненных данных. В остальных случаях операция неатомарная

▪ Вытеснение потоков, выполняющих данные операции, или выполнение операций в двугих потоках в многоядерных системах приводит к неопределённому поведению.

8

Атомарность в С и С++

▪ В языках С и С++ предполагается, что все операции неатомарны.

▪ Операции могут быть атомарными в большинстве случаев, например, операция присваивания 32-битного целого значения.

▪ Тем не менее, все инструкции должны рассматриваться как неатомарные.

▪ К счастью, в С и С++ есть набор шаблонов атомарных типов данных.

9

Атомарность в С и С++

▪ Атомарные операции в С++ неделимы. Из любого потока нельзя обнаружить эту операцию выполненной частично - она либо выполнена, либо невыполнена.

▪ Это позволяет избежать гонок данных.

▪ Для атомарных типов определён метод is_lock_free, позволяющий определить, являются ли операции над ним напрямую с помощью атомарных инструкций, или они эмулируются.

10

Атомарные типы в С и С++

▪ std::atomic_flag - единственный тип, который не имеет функции is_lock_free. Он предельно простой, всецело атомарный и поддерживает одну операцию: test_and_set - проверить и установить.

▪ Остальные типы определяются специализацией шаблона std::atomic<>, например std::atomic<int> и std::atomic<void*>

11

Атомарные типы в С и С++

Атомарные тип Соответствующая специализация

std::atomic_bool std::atomic<bool>

std::atomic_char std::atomic<char>

std::atomic_schar std::atomic<signed char>

std::atomic_uchar std::atomic<unsigned char>

std::atomic_short std::atomic<short>

std::atomic_ushort std::atomic<unsigned short>

std::atomic_int std::atomic<int>

std::atomic_uint std::atomic<unsigned int>

std::atomic_long std::atomic<long>

...

+ пользовательские типы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

Атомарный флаг std::atomic_flag

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

Операции сохранения и загрузки атомарных типов

// Объявить переменную и проициализировать truestd::atomic<bool> b(true);

// Загрузить значение в переменной в неатомарную // переменную xbool x = b.load(std::memory_order_acquire);

// Записать в переменную b значение trueb.store(true);

// Обменять значение переменной b со значением false, // вернуть предыдущее значение b в переменную x.x = b.exchange(false, std::memory_order_acq_rel);

16

Операция “сравнить и обменять”

Операция 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

Операция “сравнить и обменять”

compare_exchange_weak() - сохранение может не произойти, даже если текущее значение совпадает с ожидаемым. Значение переменной не изменится, функция возвращает false.

Последнее возможно при отсутствии аппаратной поддержки команды сравнить-и-обменять, из-за того, что поток может быть вытеснен в середине требуемой последовательности команд (ложный отказ).

Из-за возможного ложного отказа функцию compare_exchange_weak() обычно вызывают в цикле:

bool expected = false;extern atomic<bool> b;...while (!b.compare_exchange_weak(expected, true);

18

Операция “сравнить и обменять”

compare_exchange_strong() гарантирует замену переменной в случае выполнения условия.

▪ compare_exchange_strong() выгодно использовать в случае однократном выполнении операции, т.е. при необходимости заменить значение на жалаемое, и в случае, если вычисление нового значения занимает длительное время.

▪ Если функция compare_exchange вызывается в цикле, тогда предпочтительнее использовать compare_exchange_weak, чтобы избежать двойного цикла (compare_exchange_strong реализован в виде цикла на системах, которые не поддерживают атомарной операции сравнения и замены).

19

Атомарный тип atomic<T*>

Функции для типа atomic<T*>:

▪ 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::atomic<C*> 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

Процессорные ядра

Внеочередное выполнение инструкций (out-of-order execution)

Когерентность кэша (MESI, MOESI, MESIF)

33

Аппаратно вызванные трансформации программы

Instruction Fetch & PreDecode

Instruction Queue (IQ)

Decode

Rename/Allocate

SchedulerReservation

Stations

Execution Units

Retirement Unit (Re-Order

Buffer)

Intel 64 CISC macro-instructions

Execution Engine

(out-of-order)

ITLB Instruction Cache (32KiB)

L2 TLBL2 Cache

(256 KiB, 8-way)

DTLB Data Cache (32KiB)

L3 Cache

Front-End Pipeline

(in-order)

Nehalem RISCmicro-

operations

Intel 64 CISC macro-instr.

Intel Nehalem Core Pipeline

34

Аппаратно вызванные трансформации программы

16 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

Decoded Instruction Queue (DIQ, 28 uops. max) Loop Stream Detection, Micro-Fusion, Macro-

Fusion

Intel64 CISCmacro-instr.

Nehalem RISC

micro-operations

4 micro-ops./cycle

ITLB L1 I-cache (32 KiB, 4-way)

6 instr./cycle

Branch Prediction Unit (BPU)

5 instructions/cycle

4 uops./cycle

Unified L2-Cache

Simple Simple micro-cod

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, ...

Реальное выполнение программы

Кэшчастные и общие кэши, буферы записи, ...

Последовательная согласованность (sequential consistency, SC)

“Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979)

▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров.

▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой.

44

Последовательная согласованность (sequential consistency, SC)

“Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979)

▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров.

▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой.

И это прекрасно!

45

Последовательная согласованность (sequential consistency, SC)

“Результат выполнения программы такой, как если бы операции всех процессоров выполнялись последовательно и результат операции каждого отдельного процессора появлялся бы в этой последовательности в порядке, который определяется программой.” (Л. Лэмпорт, 1979)

▪ Рассмотрим многопроцессорную систему, состоящую из нескольких последовательных процессоров.

▪ Операции, выполняемые процессорами над некоторой областью памяти (страница, объект, адрес, ...), появляются в одном и том же порядке для всех процессоров, несмотря на то, что фактическая последовательность выполнения операций может быть другой.

И это прекрасно!но...

46

Последовательная согласованность (sequential consistency, SC)

… но вы этого не хотите!

▪ Скорее всего очень нерационально выполнять в точности то, что вы написали.

▪ Гораздо лучше выполнить нечто иное, которое бы работало так же, как и то, что вы написали, но выполнялось бы гораздо быстрее.

Поэтому▪ Мы (программное обеспечение ПО: компилятор и

аппаратное обеспечение АО: кэш, процессор) будем это делать!

▪ А вы (программисты), в свою очередь, должны обеспечить возможность корректной трансформации и выполнения программы так, чтобы сохранялась иллюзия последовательной согласованности, включив в свою программу необходимые ограничения. 47

Модель памяти - это договор

Вы обещаетеКорректно

реализовать синхронизацию

в вашей программе

(путём добавления необходимых инструкций в программу,

делающих её безопасной

относительно гонок)

Система обещает

Обеспечить иллюзию

выполнения той программы, которую вы написали.

(путём компиляции и выполнения)

48

Модель памяти - это договор

Вы обещаетеКорректно

реализовать синхронизацию

в вашей программе

(путём добавления необходимых инструкций в программу,

делающих её безопасной

относительно гонок)

Система обещает

Обеспечить иллюзию

выполнения той программы, которую вы написали.

(путём компиляции и выполнения)

Модель памяти определяет, какие действия вы должны совершить и как должна отреагировать система, чтобы

обеспечить выполнение операций с памятью в необходимой последовательности. 49

Аппаратное переупорядочивание инструкций

50

Переупорядочивание на примере алгоритма Деккера

Поток 1flag1 = 1;if (flag2 != 0) // ожидать освобождения // критической секцииelse // войти в критическую // секцию

Алгоритм Деккера позволяет решать проблему взаимного исключения и был опубликован в 1965 г.Он не приводит к взаимным исключениям (deadlock) и свободен от голодания (starvation).

Поток 2flag2 = 1;if (flag1 != 0) // ожидать освобождения // критической секцииelse // войти в критическую // секцию

51

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

flag2 = 1;

if (flag1 != 0)

{ … }

Память: flag1 = 0, flag2 = 0

flag1 = 1;

if (flag2 != 0)

{ … }

Store Buffer Store Buffer

52

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

Память: flag1 = 0, flag2 = 0

flag1 = 1;

if (flag2 != 0)

{ … }

flag2 = 1;

if (flag1 != 0)

{ … }

Store Bufferflag1 = 1

Store Buffer

Сохранение 1 в буфере

53

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

Память: flag1 = 0, flag2 = 0

flag1 = 1;

if (flag2 != 0)

{ … }

flag2 = 1;

if (flag1 != 0)

{ … }

Store Bufferflag1 = 1

Сохранение 1 в буфере

Сохранение 1 в буфере

Store Bufferflag1 = 1

54

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

Память: flag1 = 0, flag2 = 0

flag1 = 1;

if (flag2 != 0)

{ … }

flag2 = 1;

if (flag1 != 0)

{ … }

Store Bufferflag1 = 1

Сохранение 1 в буфере

Сохранение 1 в буфере

Store Bufferflag1 = 1

Чтение 0 для flag2

Чтение 0 для flag1

StoreLoad

55

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

Память: flag1 = 0, flag2 = 0

flag1 = 1;

if (flag2 != 0)

{ … }

flag2 = 1;

if (flag1 != 0)

{ … }

Store Bufferflag1 = 1

Сохранение 1 в буфере

Сохранение 1 в буфере

Store Bufferflag1 = 1

Чтение 0 для flag2

Чтение 0 для flag1

56

Переупорядочивание на примере алгоритма Деккера

Процессор 1 Процессор 2

Память: flag1 = 1, flag2 = 1

flag1 = 1;

if (flag2 != 0)

{ … }

flag2 = 1;

if (flag1 != 0)

{ … }

Store Bufferflag1 = 1

Сохранение 1 в буфере

Сохранение 1 в буфере

Store Bufferflag1 = 1

Чтение 0 для flag2

Чтение 0 для flag1

Сброс буфера (flag1)

Сброс буфера (flag2)

57

Переупорядочивание в различных процессорах

Тип переупоря-дочивания / архитектуры

Alpha ARMv7 POWER SPARC RMO

SPARC PSO

SPARC TSO

x86 AMD64 IA-64

Loads после loads

Loads после stores

Stores после stores

Stores после loads

АО с loads

АО с stores

Зависимые loads

АО - атомарные операции 58

Переупорядочивание в различных процессорах

IBM Blue Gene

Смартфоны

DEC AlphaXbox 360

PowerPC

ARM

SPARC TSO

x86 / 64

более сильное упорядочивание(strong model, sequential consistency)

более слабое упорядочивание(weak model, relaxed ordering)

Как правило сильное упорядочивание

Ослабленное упорядочивание с поддержкой зависимостей

по данным

Вполне ослабленное

упорядочивание

PowerMac

59

Переупорядочивание в различных процессорах

IBM Blue Gene

Смартфоны

DEC AlphaXbox 360

PowerPC

ARM

SPARC TSO

x86 / 64

более сильное упорядочивание(strong model, sequential consistency)

более слабое упорядочивание(weak model, relaxed ordering)

Как правило сильное упорядочивание

Ослабленное упорядочивание с поддержкой зависимостей

по данным

Вполне ослабленное

упорядочивание

PowerMac

При сильном упорядочивании (сильная модель памяти) каждая инструкция неявно реализует семантику захвата и освобождения.

При слабом упорядочивании (слабая модель памяти) не накладывается никаких ограничений на порядок выполнения инструкций.

60

Переупорядочивание в процессорах архитектуры x86

IBM Blue Gene

Смартфоны

DEC AlphaXbox 360

PowerPC

ARM

SPARC TSO

x86 / 64

более сильное упорядочивание(strong model, sequential consistency)

более слабое упорядочивание(weak model, relaxed ordering)

Как правило сильное упорядочивание

Ослабленное упорядочивание с поддержкой зависимостей

по данным

Вполне ослабленное

упорядочивание

PowerMac

61

Переупорядочивание в процессорах архитектуры x86

Intel Architectures Software Developer’s Manual, Vol. 3:

▪ Операции чтения не могут быть переупорядочены с другими операциями чтения

▪ Операции записи не могут быть переупорядочены с другими операциями записями

▪ Операции записи не могут быть переупорядочены с другими операциями записи, кроме следующих исключений: …

▪ Операции чтения могут быть переупорядочены с более старыми операциями записи в другие области памяти, но не с операциями записи в ту же область.

▪ Операции чтения не могут перейти раньше инструкций LFENCE, MFENCE, операции записи - раньше инструкций LFENCE, SFENCE, MFENCE.

▪ LFENCE, SFENCE, MFENCE не могут выполниться раньше операций чтения, записи, того или другого соответственно.

62

Переупорядочивание в процессорах архитектуры x86

Процессор 1mov x, 1 ; запись (load) ; 1 в x

mov r1, y ; загрузка (store) ; из y в регистр

Процессор 2mov 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

x = 0, y = 0

63

Переупорядочивание в процессорах архитектуры x86

Процессор 1mov x, 1 ; запись (load) ; 1 в x

mov r1, y ; загрузка (store) ; из y в регистр

Процессор 2mov y, 1 ; запись (load) ; 1 в x

mov r2, x ; загрузка (store) ; из y в регистр

▪ r1 = 0, r2 = 0

В процессорах архитектуры x86 достустима перестановка операция load после store. Или, то же, операции store могут выполняться до операций load.

x = 0, y = 0

StoreLoad

64

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("" ::: "memory");

r1 = y;

Программный барьер памяти: запретить переупорядочивание инструкций компилятором.

65

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("" ::: "memory");

r1 = y;

Поток 3if (r1 == 0 && r2 == 0) printf("reordering happened!\n");

Поток 2y = 1;

asm volatile("" ::: "memory");

r2 = x;

66

Переупорядочивание может произойти!

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("" ::: "memory");

r1 = y;

Поток 3if (r1 == 0 && r2 == 0) printf("reordering happened!\n");

Поток 2y = 1;

asm volatile("" ::: "memory");

r2 = x;

67

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("mfence" ::: "memory");asm volatile("" ::: "memory");

r1 = y;

Поток 2y = 1;

asm volatile("mfence" ::: "memory");asm volatile("" ::: "memory");

r2 = x;

Поток 3if (r1 == 0 && r2 == 0) printf("reordering happened!\n");

68

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("mfence" ::: "memory");asm volatile("" ::: "memory");

r1 = y;

Программный барьер памяти: запретить переупорядочивание инструкций компилятором.

Аппаратный барьер памяти: запретить переупорядочивание инструкций процессором.

69

mov DWORD PTR X[rip], 1mfencemov eax, DWORD PTR Y[rip]...mov DWORD PTR r1[rip], eax

полный барьер памяти

Переупорядочивание в процессорах архитектуры x86

Поток 1x = 1;

asm volatile("mfence" ::: "memory");asm volatile("" ::: "memory");

r1 = y;

Поток 3if (r1 == 0 && r2 == 0) printf("reordering happened!\n");

Переупорядочивание не произойдёт!

Поток 2y = 1;

asm volatile("mfence" ::: "memory");asm volatile("" ::: "memory");

r2 = x;

70

Переупорядочивание в процессорах с ослабленным упорядочиванием

IBM Blue Gene

Смартфоны

DEC AlphaXbox 360

PowerPC

ARM

SPARC TSO

x86 / 64

более сильное упорядочивание(strong model, sequential consistency)

более слабое упорядочивание(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

Переупорядочивание в процессорах с ослабленным упорядочиванием

Поток 1int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } }

Поток 2int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } }

75

Переупорядочивание в процессорах с ослабленным упорядочиванием

Поток 1int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } }

Поток 2int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) { sharedVal++; asm volatile(...); flag.store(0, ...) count++; } }

76

Переупорядочивание в процессорах с ослабленным упорядочиванием

Поток 1int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) {(2) sharedVal++; asm volatile(...); (1) flag.store(0, ...); count++; } }

Поток 2int sharedCount; void IncShared() { int count = 0;

while (count < N) { randomBusyWork(); int expected = 0; if (...) {(3) sharedVal++; asm volatile(...); flag.store(0, ...) count++; } }

77

Переупорядочивание в процессорах с ослабленным упорядочиванием

$ ./prog 100000 // N = 100000

sharedCount = 199348

sharedCount = 199034

sharedCount = 199517

sharedCount = 199829

sharedCount = 199113

sharedCount = 199566

Допустим N = 100000

Каждый из 2 потоков выполняет 100000 операций инкремента разделяемой переменной sharedCount++.

Ожидаемое значение: sharedVal = 200000

В реальности:

78

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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-кэш

Буфер 1 Буфер 2 Буфер N

... ...

79

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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-кэш

Буфер 1 Буфер 2 Буфер N

... ...

80

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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-кэш

Буфер 1 Буфер 2 Буфер N

... ...

81

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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-кэш

Буфер 1 Буфер 2 Буфер N

... ...

82

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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

Переупорядочивание при выполнении операция захвата-освобождения в процессорах архитектуры POWER

Поток 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;

... ...

StoreStore

84

Программное переупорядочивание инструкций

85

Программное переупорядочивание инструкций

Первое правило робототехники компиляторов и процессоров при упорядочивании доступа к памяти:

Нельзя изменять поведение однопоточной программы.

▪ В однопоточных программах переупорядочивания остаются незамеченными.

▪ То же самое - при многопоточном программировании на основе мьютексов, семафоров и т.д.

▪ Но не при использовании атомарных переменных и техник программирования без блокировок.

86

Программное переупорядочивание инструкций

mov eax, DWORD PTR y[rip]add eax, 111mov DWORD PTR x[rip], eaxmov DWORD PTR y[rip], 222

int x, y;

int main() { x = y + 111; y = 222; printf("%d%d", x, y);

gcc -S -masm=intel prog.c

выполнение командыx = y + 111 завершеновыполнение командыy = 222 завершено

87

Программное переупорядочивание инструкций

mov eax, DWORD PTR y[rip]mov edx, 222...mov DWORD PTR y[rip], 222lea esi, [rax+111]...mov DWORD PTR x[rip], esi

int x, y;

int main() { x = y + 111; y = 222; printf("%d%d", x, y);

gcc -S -masm=intel -O2 prog.c

выполнение командыy = 222 завершено

выполнение командыx = y + 111 завершено

y = 222 выполняется раньше x = y + 111

88

Программное переупорядочивание инструкций

mov eax, DWORD PTR y[rip]add eax, 111mov DWORD PTR x[rip], eaxmov esi, DWORD PTR x[rip]mov edx, 222...xor eax, eaxmov DWORD PTR y[rip], 222

int x, y;

int main() { x = y + 111; asm volatile("" ::: "memory"); y = 222; printf("%d%d", x, y);

gcc -S -masm=intel -O2 prog.c

явный барьер

89

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

int data;bool isReleased = false; // данные опубликованы?

void releaseData(int val) // опубликовать данные{ data = val; // записать данные isReleased = true; // данные опубликованы,} // можно с ними работать

90

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

Данные должны быть проинициализированы в 1 потоке перед тем, как они будут использованы во 2 потоке.

91

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

1. Данные проиницаилизрованы

92

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

1. Данные проициализированы

2. Ура! Второй поток может обрабатывать

93

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

94

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы.

95

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы.

StoreStore

96

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

Из-за переупорядочивания инструкций компилятором флаг выставляется до того, как данные готовы.

97

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int data;bool isReleased = false;

void releaseData(int val) { data = val; isReleased = true; }

Поток 2

void utilizeData(){ while (!isReleased); doSomething(data);}

98

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int 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

Программное переупорядочивание инструкций на примере с публикацией данных (операция захвата-освобождения)

Поток 1int 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

Операции сохранения из чистого воздуха (out-of-thin air stores)

if (x > 0)

y++;

register int r = y;

if (x > 0)

r++;

y = r;

Размещение переменной в регистре в процессе оптимизирующей компиляции

создание новой регистровой переменной

Новая операция сохранения (store) “из чистого воздуха”

Создание операций сохранения “из чистого воздуха” не допустимо в соответствии с последним стандартом, однако... 101

Операции сохранения из чистого воздуха (out-of-thin air stores)

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++;

однако в С & PThreads такая оптимизация допускается:

...int trylock() { register int r = count; int rc; rc = pthread_mutex_trylock(&mutex); if (rc == 0) count++; count = r;

новый store и новая гонка данных! 102

Базовое отношение happens-before (происходит-раньше)

103

Отношение happens-before (происходит-раньше)

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

x = 10; // A

y = x + 1; // B

104

Отношение happens-before (происходит-раньше)

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

x = 10;

y = x + 1;

z = sqrt(x * y);

Все соотношения happens-before для операции А (x = 10)

105

x = 10;

y = x + 1;

z = sqrt(x * y);

m = k - 5;

print(m)

print(x)

Отношение happens-before (происходит-раньше)

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

106

Поток 1 Поток 2

x = 10;

y = x + 1;

z = sqrt(x * y);

m = k - 5;

print(m)

w = x + 2

Отношение happens-before (происходит-раньше)

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

107

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

Происходит-раньше ≠ происходит раньше

1. Из того, что А происходит-раньше В не следует, что А происходит раньше В.

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

108

Происходит-раньше не означает происходит раньше

int x, y;

int main() { x = y + 111; // A y = 222; // B printf("%d%d", x, y);

1. Из того, что А происходит-раньше В не следует, что А происходит раньше В.

109

Происходит-раньше не означает происходит раньше

mov eax, DWORD PTR y[rip]mov edx, 222...mov DWORD PTR y[rip], 222lea esi, [rax+111]...mov DWORD PTR x[rip], esi

int x, y;

int main() { x = y + 111; // A y = 222; // B printf("%d%d", x, y);

gcc -S -masm=intel -O2 prog.c

1. Из того, что А происходит-раньше В не следует, что А происходит раньше В.

110

Происходит-раньше не означает происходит раньше

mov eax, DWORD PTR y[rip]mov edx, 222...mov DWORD PTR y[rip], 222lea esi, [rax+111]...mov DWORD PTR x[rip], esi

int x, y;

int main() { x = y + 111; y = 222; printf("%d%d", x, y);

gcc -S -masm=intel -O2 prog.c

выполнение командыy = 222 завершено

выполнение командыx = y + 111 завершено

y = 222 выполняется раньше x = y + 111

1. Из того, что А происходит-раньше В не следует, что А происходит раньше В.

111

Происходит раньше не означает происходит-раньше

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

Поток 2

if (ready) // B

print(x);

Поток 1

x = 42

ready = true; // A

int x;bool ready = false;

112

Происходит раньше не означает происходит-раньше

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

Поток 2

if (ready) // B

print(x);

Поток 1

x = 42

ready = true; // A

int x;bool ready = false;

113

Происходит раньше не означает происходит-раньше

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

Поток 2

if (ready)

print(x);

Поток 1

x = 42

ready = true;

int x;bool ready = false;

114

Происходит раньше не означает происходит-раньше

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

Поток 2

if (ready)

print(x);

Поток 1

x = 42

ready = true;

int x;bool ready = false;

115

Отношение happens-before определяет, какие операции видят последствия других операций.

Допустим A и В - операции в многопоточной программе. Тогда если А происходит-раньше В, тогда эффект (результат операции, который отразился в памяти) операции А становится видим для потока, выполняющего операцию В.

Происходит-раньше ≠ происходит раньше

1. Из того, что А происходит-раньше В не следует, что А происходит раньше В.

2. Из того, что А происходит раньше В не следует, что А происходит-раньше В

Отношение происходит-раньше имеет место тогда (и только тогда), когда это определёно стандартом языка.

116

Виды упорядочивания инструкций в С++

117

Последовательная согласованность (sequential consistency)

std::atomic<int> 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

Последовательная согласованность (sequential consistency)

std::atomic<int> data{0};std::atomic<bool> 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

Последовательная согласованность (sequential consistency)

std::atomic<int> z{0};std::atomic<bool> 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

Ослабленное упорядочение (relaxed ordering)

std::atomic<int> 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

Ослабленное упорядочение (relaxed ordering)

std::atomic<int> data{0};std::atomic<bool> 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

Ослабленное упорядочение (relaxed ordering)

std::atomic<int> z{0};std::atomic<bool> 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

Метафора человека с блокнотом

Иван

34945-32109711

Иван

Значение переменной x

x.load(memory_order_relaxed)

124

Метафора человека с блокнотом

Иван

34945-32109711

Иван

Значение переменной x

125

x.load(memory_order_relaxed)

Метафора человека с блокнотом

Иван

34945-32109711

Иван

Значение переменной x

126

x.load(memory_order_relaxed)

encore!

Метафора человека с блокнотом

Иван

34945-32109711

Значение переменной x

x.load(memory_order_relaxed)

Иван

127

Метафора человека с блокнотом

Иван

34945-32109711-8

Иван

Значение переменной x

x.store(-8, memory_order_relaxed)

128

Метафора человека с блокнотом

Иван

34945-32109711-8

Значение переменной x

x.load(memory_order_relaxed)

Иван

129

Метафора человека с блокнотом

Иван

Сергей

Анна

...34945-32109711-8

Иван

Анна Сергей

Значение переменной x

130

Барьеры памяти

131

Виды переупорядочиваний

StoreLoad StoreStore

LoadStoreLoadLoad

132

Иерархическая структура современных многоядерных систем

кэш 2

кэш 1

Память

кэш 2

кэш 1 кэш 1 кэш 1

Процессорные ядра

133

Метафора репозитория

1

кэш L1

2

кэш L1

Память

134

Метафора репозитория

1

кэш L1

2

кэш L1

Память

mov [x], 1mov r1, [y]

mov [y], 1mov 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

Рекс

Мухтар

Центральный репозиторий

mov [x], 1mov r1, [y]

mov [y], 1mov r2, [x]

x = 0y = 0

x = 0y = 0

x = 0y = 0

142

Метафора репозитория

1

2

Рекс

Мухтар

Центральный репозиторий

mov [x], 1mov r1, [y]

mov [y], 1mov r2, [x]

x = 0y = 1

x = 1y = 0

x = 0y = 0

143

Метафора репозитория

1

2

Рекс

Мухтар

Центральный репозиторий

mov [x], 1mov r1, [y]

mov [y], 1mov r2, [x]

x = ? {0,1}y = 1

x = 1y = ? {0,1}

x = ? {0,1}y = ? {0,1}

144

Виды барьеров

StoreLoad StoreStore

LoadStoreLoadLoad

145

Барьер LoadLoad

1

Рекс

Центральный репозиторий

LoadLoad

x = 1y = 0

x = 1y = 0

git pull, hg pull, svn update, cvs update

Барьер LoadLoad:

▪ Предотвращает переупорядочивания между загрузками до барьера и загрузками после барьера.

▪ Гарантирует, что загруженные из центрального репозитория (памяти) в локальный репозиторий (кэш) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из центрального репозитория. 146

Барьер LoadLoad

1

Рекс

Центральный репозиторий

LoadLoad

x = 1y = 0

x = 1y = 0

git pull, hg pull, svn update, cvs update

// Так получилось, что // widget.ready = true утекло // в локальный репозиторийif (widget.ready) { // Подгрузить всё остальное // содержимое widget - // такое же “свежее”, как ready LoadLoadFence(); do_something(widget);} 147

Барьер StoreStore

1

Рекс

Центральный репозиторий

StoreStore

x = 1y = 0

Барьер StoreStore:

▪ Предотвращает переупорядочивания между сохранениями до барьера и сохранениями после барьера.

▪ Гарантирует, что загруженные из локального репозитория (кэш) в локальный репозиторий (память) значения будут по крайней мере такие же новые, как и последнее значение, которое “просочилось” из локального репозитория.

x = 1y = 0

git push, hg push, svn commit, cvs commit

148

Барьер StoreStore

1

Рекс

Центральный репозиторий

StoreStore

x = 1y = 0

x = 1y = 0

git push, hg push, svn commit, cvs commit

widget.x = x;widget.y = y;StoreStoreFence();widget.ready = true;

// Если ready = true просочится // Мухтару, то он увидит увидит// на центральном репозитории// всё остальные поля widget, // которые для него подготовил Рекс 149

Барьер LoadStore

1

Рекс

mov r1, [y]mov r2, [x]mov [z], 42mov [w], r3mov [v], r4

Операции загрузки load

Операции сохранения store

Переупорядочивание LoadStore1. Есть набор инструкций, состоящий из операций

сохранения и загрузки.

150

Барьер LoadStore

1

Рекс

mov r1, [y]mov r2, [x]mov [z], 42mov [w], r3mov [v], r4

Операции загрузки load - отложены

Операции сохранения store - выполнены

Переупорядочивание LoadStore1. Есть набор инструкций, состоящий из операций

сохранения и загрузки.

2. Если Рекс встречает операцию загрузки, то он просматривает следующие операции сохранения, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 151

Барьер LoadStore

1

Рекс

mov r1, [y]mov r2, [x]mov [z], 42mov [w], r3mov [v], r4

Будут промахи по кэшу...

Будут попадания в кэш...

Переупорядочивание LoadStore1. Есть набор инструкций, состоящий из операций

сохранения и загрузки.

2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 152

Барьер LoadStore

1

Рекс

mov r1, [y]mov r2, [x]LoadStoremov [z], 42mov [w], r3mov [v], r4

Будут промахи по кэшу...

Будут попадания в кэш...

Переупорядочивание LoadStore1. Есть набор инструкций, состоящий из операций

сохранения и загрузки.

2. Если Рекс встречает операцию загрузки, которые промахиваются по кэшу, то он просматривает следующие операции сохранения, которые попадают в кэш, и если они абсолютно не связаны с текущей операцией загрузки, то он откладывает выполнение операции загрузки и в первую очередь выполняет операции сохранения. 153

Барьер StoreLoad

1

Рекс

mov [x], 1mov r1, [y]

2

Мухтар

mov [y], 1mov r2, [x]

x = 1y = 0

x = 0y = 1

r1 = 0

r2 = 0

154

Барьер StoreLoad

1

Рекс

mov [x], 1StoreLoadmov r1, [y]

2

Мухтар

mov [y], 1StoreLoadmov r2, [x]

x = 1y = ?

x = ?y = 1

Барьер StoreLoad:

▪ Гарантирует видимость для других процессоров всех операций сохранения, выполненных до барьера.

▪ Обеспечивает для всех операций загрузки, выполненных после барьера, получение результатов, которые имеют место во время барьера.

▪ Барьер предотвращает r1 = r2 = 0

▪ StoreLoad ≠ StoreStore + LoadLoad

155

Барьер StoreLoad

1

Рекс

mov [x], 1StoreLoadmov r1, [y]

x = 1y = ?

Барьер StoreLoad (≠ StoreStore + LoadLoad):1. Отправка (push) всех изменений в центральный репозиторий.

Центральный репозиторий

156

Барьер StoreLoad

1

Рекс

mov [x], 1StoreLoadmov r1, [y]

x = 1y = ?

Барьер StoreLoad (≠ StoreStore + LoadLoad):1. Отправка (push) всех изменений в центральный репозиторий.

2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой).

Центральный репозиторий

157

Барьер StoreLoad

1

Рекс

mov [x], 1StoreLoadmov r1, [y]

x = 1y = ?

Барьер StoreLoad (≠ StoreStore + LoadLoad):1. Отправка (push) всех изменений в центральный репозиторий.

2. Ожидание завершения выполнения операции отправки (в отличие от StoreStore, который может выполняться с задержкой).

3. Загрузка (pull) всех последних изменений из центрального репозитория (в отличие от LoadLoad, который не загружает абсолютно последние изменения)

Центральный репозиторий

158

Семантика захват-освобождение (acquire-release semantics)

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

Захват и освобождение - в терминах барьеров

StoreLoadFence StoreStoreFence

LoadStoreFence LoadLoadFence

Acquire (захват)

Release (освобождение)163

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

StoreStoreFence + LoadStoreFence

read-acquire

write-release 164

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

StoreStoreFence + LoadStoreFence

read-acquire

write-release 165

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

StoreStoreFence + LoadStoreFence

read-acquire

write-release 166

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

StoreStoreFence + LoadStoreFence

read-acquire

write-release

1

2

167

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

StoreStoreFence + LoadStoreFence

read-acquire

write-release

2

1

2

1

168

LoadLoadFence + LoadStoreFence

Захват и освобождение - в терминах барьеров

read / write

read / write

read / write

read / write

read / write

read / write

read / write

read / write

LoadStoreFence + StoreStoreFence

read-acquire

write-release 169

Поток 2

ready.load( memory_order_acquire);

print(x);

Поток 1

x = 42 ready.store(true, memory_order_release);

Семантика захват-освобождение на примере с публикацией

int x;bool ready = false;

170

Поток 1

x = 42 ready.store(true, memory_order_release);

Семантика захват-освобождение на примере с публикацией

Поток 2

ready.load( memory_order_acquire);

print(x);

int x;bool ready = false;

Синхронизируется-с

Synchronizes-with

Всё, что здесь...

… будет видно здесь

171

Поток 1

x = 42 ready.store(true, memory_order_release);

Семантика захват-освобождение на примере с публикацией

Поток 2

ready.load( memory_order_acquire);

print(x);

int x;bool ready = false;

Синхронизируется-с

Synchronizes-with

… будет видно здесь

Всё, что здесь...

Операция записи-освобождения

Операция чтения-захвата

172

Семантика захват-освобождение на примере с публикацией

Поток 1void 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

Поток 2

void useWidget() { while (!ready.load( memory_order_acquire)) {}

doSomething(w);}

Семантика захват-освобождение на примере с публикацией

Поток 1void initWidget(x, y, z) { w.x = x; w.y = x; w.z = z; ready.store(true, memory_order_release);}

widget w;bool ready = false;

Синхронизируется-с

Synchronizes-with

Всё, что здесь...

… будет видно здесь

Операция записи-освобождения

Операция чтения-захвата

174

Поток 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;

Поток 1void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release);}

Синхронизируется-с

Synchronizes-with

175

Поток 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;

Поток 1void initWidget(x, y, z) { x = 10 y = x + 20; z = x + 12; ready.store(true, memory_order_release);}

Синхронизируется-с

Synchronizes-with

Операция записи-освобождения

Операция чтения-захвата

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

Отношение синхронизируется-с (synchronized-with)

178

Отношение synchronized-with (синхронизируется-с)

Отношение syncrhonized-with более сильное по сравнению с happens-before, т.е.:

synchronizes-with ⟶ happens-before

179

Отношение synchronized-with (синхронизируется-с)

Отношение syncrhonized-with более сильное по сравнению с happens-before, т.е.:

synchronizes-with ⟶ happens-before

Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое

1. или операцией A.

2. или следующей за A операцией записи над x в том же потоке, который выполнил A.

3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A.

180

Отношение synchronized-with (синхронизируется-с)

Отношение syncrhonized-with более сильное по сравнению с happens-before, т.е.:

synchronizes-with ⟶ happens-before

Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое

1. или операцией A.

2. или следующей за A операцией записи над x в том же потоке, который выполнил A.

3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A.

181

Отношение synchronized-with (синхронизируется-с)

Отношение syncrhonized-with более сильное по сравнению с happens-before, т.е.:

synchronizes-with ⟶ happens-before

Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое

1. или операцией A.

2. или следующей за A операцией записи над x в том же потоке, который выполнил A.

3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A.

Иначе: Если поток 1 сохраняет значение, а поток 2 читает это значение, то существует отношение синхронизируется-с между операциями сохранения и загрузки. 182

Поток 1void prepare() { w.x = x; w.y = x; cntr++;

w.ready.store(true, memory_order_release);}

Операци Acquire-release (synchronized-with) в С++

Поток 2

void utilize() { while (!w.ready.load( memory_order_acquire)) {}

doSomethingWith(w);

writeLog(cntr);}

Widget w;int cntr;

Синхронизируется-с

Synchronizes-with

183

Поток 1void prepare(Widget &w) { w.x = x; w.y = x; cntr++;

w.ready.store(true, memory_order_release);}

Операци Acquire-release (synchronized-with) в С++

Поток 2

void utilize() { if (!w.ready.load( memory_order_acquire)){

doSomethingWith(w);

writeLog(cntr); }

Widget w;int cntr;

Синхронизируется-с

Synchronizes-with

184

Поток 2void utilize() { bool ready = w.ready.load( memory_order_relaxed); if (ready) { atomic_thread_fence( memory_order_acquire)){

doSomethingWith(w);

writeLog(cntr); }

Поток 1void prepare(Widget &w) { w.x = x; w.y = x; cntr++;

atomic_thread_fence(, memory_order_release);

w.ready.store(true, memory_order_relaxed);}

Барьеры acquire-release (synchronized-with) в С++

Widget w;int cntr;

185

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

186

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Запись в w, cntr

1

187

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Барьер release - все данные загружаются в “репозиторий”Запись в w,

cntr

12

188

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Барьер release - все данные загружаются в “репозиторий”Запись в w,

cntr

12

Запись true в ready

3

189

Барьеры acquire-release (synchronized-with) в С++

Рекс

Барьер release - все данные загружаются в “репозиторий”

Мухтар

Центральный репозиторий (ЦР)

Запись в w, cntr

1

ready просачивается в “репозиторий”

2

Запись true в ready

3

4

190

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Запись в w, cntr

ready просачивается в “репозиторий”

ready просачивается из ЦР Мухтару

1 Барьер release - все данные загружаются в “репозиторий”

2

Запись true в ready

3

4

5

191

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Запись в w, cntr

1 Барьер release - все данные загружаются в “репозиторий”

2

Запись true в ready

3

ready просачивается из ЦР Мухтару

Чтение true в ready

ready просачивается в “репозиторий”

4

56

192

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Барьер release - все данные загружаются в “репозиторий”Запись в w,

cntr

12

ready просачивается в “репозиторий”

ready просачивается из ЦР Мухтару

Барьер acquire - данные загружаются из ЦР Мухатору

Запись true в ready

3

4

5

7

Чтение true в ready

6

193

Барьеры acquire-release (synchronized-with) в С++

Рекс

Мухтар

Центральный репозиторий (ЦР)

Запись в w, cntr

Барьер release - все данные загружаются в “репозиторий”

ready просачивается в “репозиторий”

12

Запись true в ready

3

4

ready просачивается из ЦР Мухтару

5

Чтение true в ready

Барьер acquire - данные загружаются из ЦР Мухатору

7Чтение w и cntr

6

8

194

std::vector<int> queue;std::atomic<int> 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

Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое

1. или операцией A.

2. или следующей за A операцией записи над x в том же потоке, который выполнил A.

3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A.

Отношение synchronized-with (синхронизируется-с)

196

Операция записи A над переменной x синхронизируется-с такой операцией операцией чтения B над x, которая читает значение, сохранённое

1. или операцией A.

2. или следующей за A операцией записи над x в том же потоке, который выполнил A.

3. или последовательностью операций чтения-модификации-записи над x в любом потоке, при условии, что значение, прочитанное первым потоком в этой последовательности, является значением, записанным операцией A.

Отношение synchronized-with (синхронизируется-с)

197

Если существует последовательность операций чтения-модификации-записи, помеченных признаком acquire, причём каждая операция загрузки загружает значение, записанное предыдущей операцией, то такая цепочка называется последовательностью освобождений.

Операция чтения может быть помечена любым признаком.

std::vector<int> queue;std::atomic<int> 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

Последовательность освобождений

199

pop() {

for (...) { queue.push(...) }

count.store( release)

consume() { ... count.fetch_sub( acquire)

process(...)consume() { ... count.fetch_sub( acquire)

process(...)

T1 T2 T3

10

9

8

синхронизируется-сцепочка зависимостейпроисходит раньше

Связь семантики захвата-освобождения с проблемой взаимного исключения или что же такое мьютекс?

200

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

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

...mut.lock(); // Ay = 2; // Bmut.unlock();...

Мьютекс и семантика захвата-освобождения

Что всё-таки подразумевается под переупорядочиванием инструкций (A и В) в потоке?

T1

Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке.

203

...mut.lock(); y = 2; mut.unlock();...

Мьютекс и семантика захвата-освобождения

Что всё-таки подразумевается под переупорядочиванием инструкций (A и В) в потоке?

T1 T2

2

1

Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке.

204

Мьютекс и семантика захвата-освобождения

Что всё-таки подразумевается под переупорядочиванием инструкций (A и В) в потоке?

T1

Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке.

T2

...mut.lock(); y *= 10; mut.unlock();...

...mut.lock(); y = 2; mut.unlock();...

205

Мьютекс и семантика захвата-освобождения

Что всё-таки подразумевается под переупорядочиванием инструкций (A и В) в потоке?

Это означает, что есть какой-то второй поток, который “видит” эти операции, выполняющиеся в другом порядке.

...mut.lock(); mut.unlock();y = 2; ...

T1 T2

...mut.lock(); y *= 10; mut.unlock();...

гонка!

206

x = 1;mut.lock();y = 2;mut.unlock();z = 3;

Мьютекс и семантика захвата-освобождения

mut.lock();x = 1;y = 2;z = 3;mut.unlock();

Можно трансформировать это в это?

207

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

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

... void lock() { while (flag.test_and_set( std::memory_order_relaxed)); } void unlock() { flag.clear(std::memory_order_relaxed); }

Мьютекс и семантика захвата-освобождения

Допустим

210

... 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

... 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;

Допустим

212

Мьютекс и семантика захвата-освобождения

k = 1;l = 2;

mut.lock();

x = 3;y = 4;z = 5;

mut.unlock();

p = 6;q = 7;

213

Мьютекс и семантика захвата-освобождения

k = 1;l = 2;

mut.lock();

x = 3;y = 4;z = 5;

mut.unlock();

p = 6;q = 7;

flag.clear(std::memory_order_release)

flag.test_and_set( std::memory_order_acquire)

214

acquire

release

Мьютекс и семантика захвата-освобождения

k = 1;l = 2;

mut.lock();

x = 3;y = 4;z = 5;

mut.unlock();

p = 6;q = 7;

Мьютекс определяет семантику захвата-освобождения (lock - запись-освобождение, release - чтение-захват):

mutex ⟶ acquire-release

flag.clear(std::memory_order_release)

flag.test_and_set( std::memory_order_acquire)

215

acquire

release

Мьютекс и семантика захвата-освобождения

k = 1;l = 2;

mut.lock();

x = 3;y = 4;z = 5;

mut.unlock();

p = 6;q = 7;

Мьютекс определяет семантику захвата-освобождения (lock - запись-освобождение, release - чтение-захват):

mutex ⟶ acquire-releaseЯвляется ли пара операций захвата-освобождения достаточной для реализации взаимного исключения

acquire-release ⟶ mutex ??

flag.clear(std::memory_order_release)

flag.test_and_set( std::memory_order_acquire)

216

...mut.lock();y = 2;mut.unlock();...

Мьютекс и семантика захвата-освобождения

...mut.lock();y *= 10;mut.unlock();...

...mut.lock();y += 22;mut.unlock();...

T1 T2 T3

217

flag.load() // 0...y = 2;...flag.store(0)...

Мьютекс и семантика захвата-освобождения

flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 0...y += 22;...flag.store(0)

flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 0...y *= 10;...flag.store(0);

T1 T3T2

218

Мьютекс и семантика захвата-освобождения

T1 T3T2

load-acquire...y = 2;...store-release...

flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1load-acquire...y += 22;...store-release

flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1flag.load() // 1load-acquire...y *= 10;...store-release

219

Synchronizes-with

Синхронизируется-с

Synchronizes-with

Синхронизируется-с

Мьютекс и семантика захвата-освобождения

T1 T3T2

load-acquire...y += 22;...store-release

load-acquire...y *= 10;...store-release

Synchronizes-with

Синхронизируется-с

Synchronizes-with

Синхронизируется-с220

load-acquire...y = 2;...store-release...

Мьютекс и семантика захвата-освобождения

T1 T3T2

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) операцией сохранения предыдущей пары загрузки-сохранения, а операция сохранения синхронизируется с операцией сохранения последующей пары загрузки сохранения.

Решение проблемый двойной проверки с помощью модели памяти С++

222

Проблема двойной проверки

223

Widget* Widget::instance_ptr{nullptr};

Widget* Widget::instance() {

if (instance_ptr == nullptr) { // 1: 1 проверка lock_guard<mutex> lock{mut}; // 2: запираем

if (instance_ptr == nullptr) { // 3: 2 проверка instance_ptr = new Widget(); // 4: создать // и присвоить } } // 5: отпираем return instance_ptr; // 6: возвращаем}

Основная проблема: гонка данных между операциями 1 и 4.

Она может привести, например, к частичной инициализации instance_ptr

Проблема двойной проверки

224

std::atomic<Widget*> Widget::instance_ptr{nullptr};

Widget* Widget::instance() {

if (instance_ptr == nullptr) { // 1: 1 проверка lock_guard<mutex> lock{mut}; // 2: запираем

if (instance_ptr == nullptr) { // 3: 2 проверка instance_ptr = new Widget(); // 4: создать // и присвоить } } // 5: отпираем return instance_ptr; // 6: возвращаем}

Проблема двойной проверки

225

std::atomic<Widget*> Widget::instance_ptr{nullptr};

Widget* Widget::instance() {

if (instance_ptr == nullptr) { lock_guard<mutex> lock{mut};

if (instance_ptr == nullptr) { instance_ptr = new Widget(); } } return instance_ptr; }

нужен барьер

нужен барьер

Проблема двойной проверки в С++

226

std::atomic<Widget*> 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_guard<mutex> 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; }

Проблема двойной проверки в С++

227

std::atomic<Widget*> Widget::instance_ptr{nullptr};

Widget* Widget::instance() {

if (instance_ptr.load(memory_order_acquire) == nullptr) {

lock_guard<mutex> 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; }

instance_ptr = new Widget();

instance_ptr.store(tmp, memory_order_release);}

Мьютекс и семантика захвата-освобождения

if (instance_ptr.load(memory_order_acquire) == nullptr) {

lock_guard<mutex> lock{mut};

tmp = instance_ptr.load( memory_order_relaxed);

if (tmp == nullptr) {

T1 T2

228

всё здесь

видно здесь

Synchronizes-with

Синхронизируется-с