Як досягти бар'єру StoreLoad в C ++ 11?


13

Я хочу написати переносний код (Intel, ARM, PowerPC ...), який вирішує варіант класичної проблеми:

Initially: X=Y=0

Thread A:
  X=1
  if(!Y){ do something }
Thread B:
  Y=1
  if(!X){ do something }

в якій мета - уникнути ситуації, в якій роблять обидві ниткиsomething . (Добре, якщо жодна річ не працює; це не механізм, який виконується точно один раз.) Будь ласка, виправте мене, якщо ви бачите деякі недоліки в моїх міркуваннях нижче.

Я знаю, що я можу досягти поставленої мети за допомогою memory_order_seq_cstатомних stores та loads таким чином:

std::atomic<int> x{0},y{0};
void thread_a(){
  x.store(1);
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!x.load()) bar();
}

що досягає мети, оскільки повинен бути якийсь єдиний загальний порядок
{x.store(1), y.store(1), y.load(), x.load()}подій, який повинен узгоджуватися з програмними "краями" програми:

  • x.store(1) "в TO є раніше" y.load()
  • y.store(1) "в TO є раніше" x.load()

а якщо foo()зателефонували, то у нас є додаткові переваги:

  • y.load() "значення читає раніше" y.store(1)

а якщо bar()зателефонували, то у нас є додаткові переваги:

  • x.load() "значення читає раніше" x.store(1)

і всі ці краї, поєднані разом, утворювали б цикл:

x.store(1)"в TO є перед" y.load()"читає значення перед" y.store(1)"в TO є перед" x.load()"читає значення раніше"x.store(true)

що порушує той факт, що замовлення не мають циклів.

Я навмисно використовую нестандартні терміни "в TO є перед" і "читає значення раніше", на відміну від стандартних термінів типу happens-before, тому що я хочу попросити відгуку про правильність свого припущення про те, що ці краї дійсно мають на увазі happens-beforeвідношення, можуть бути об'єднані разом в єдине графік, і цикл у такому комбінованому графіку заборонений. Я не впевнений у цьому. Я знаю, що цей код створює правильні бар'єри для Intel gcc & clang та ARM gcc


Тепер моя справжня проблема трохи складніша, тому що я не маю контролю над "X" - вона прихована за деякими макросами, шаблонами тощо і може бути слабкішою за seq_cst

Я навіть не знаю, чи "X" - це одна змінна чи якась інша концепція (наприклад, легкий семафор або мютекс). Все, що я знаю, це те, що у мене є два макроси set()та check()такі, що check()повертається true"після" іншою ниткою set(). (Це є також відомо , що setі checkпотокобезпечна і не може створювати дані гонки UB.)

Отже, концептуально set()це дещо схоже на "X = 1" і check()схоже на "X", але я не маю прямого доступу до атомів, якщо такі є.

void thread_a(){
  set();
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!check()) bar();
}

Я переживаю, що це set()може бути внутрішньо реалізовано як x.store(1,std::memory_order_release)і / або check()може бути x.load(std::memory_order_acquire). Або гіпотетично, std::mutexщо одна нитка розблоковується, а інша - try_locking; у стандарті ISO std::mutexгарантується лише замовлення на придбання та випуск, а не seq_cst.

Якщо це так, то check(), якщо тіло можна "переупорядкувати" раніше y.store(true)( Дивіться відповідь Алекса, де вони демонструють, що це відбувається на PowerPC ).
Це було б дуже погано, оскільки зараз така послідовність подій можлива:

  • thread_b()спочатку завантажує старе значення x( 0)
  • thread_a() виконує все, в тому числі foo()
  • thread_b() виконує все, в тому числі bar()

Тож, foo()і bar()дзвонили, чого мені довелося уникати. Які мої варіанти запобігти цьому?


Варіант А

Спробуйте застосувати бар'єр для зберігання. На практиці цього можна досягти, std::atomic_thread_fence(std::memory_order_seq_cst);- як пояснив Алекс в іншій відповіді, всі перевірені укладачі випустили повний паркан:

  • x86_64: MFENCE
  • PowerPC: hwsync
  • Ітануїм: mf
  • ARMv7 / ARMv8: dmb ish
  • MIPS64: синхронізація

Проблема такого підходу полягає в тому, що я не зміг знайти жодної гарантії в правилах C ++, яка std::atomic_thread_fence(std::memory_order_seq_cst)повинна перетворюватися на повний бар'єр пам'яті. Насправді, концепція atomic_thread_fences в C ++, схоже, знаходиться на іншому рівні абстракції, ніж концепція складання бар'єрів пам'яті і стосується більше таких речей, як "яка атомна операція синхронізується з тим". Чи є якісь теоретичні докази того, що внизу реалізація досягає мети?

void thread_a(){
  set();
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!y.load()) foo();
}
void thread_b(){
  y.store(true);
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!check()) bar();
}

Варіант В

Використовуйте управління, яке ми маємо над Y, щоб досягти синхронізації, використовуючи операції читання-зміна-запис пам'яті_порядок_acq_rel на Y:

void thread_a(){
  set();
  if(!y.fetch_add(0,std::memory_order_acq_rel)) foo();
}
void thread_b(){
  y.exchange(1,std::memory_order_acq_rel);
  if(!check()) bar();
}

Ідея тут полягає в тому, що доступ до однієї атомної ( y) повинен бути єдиним порядком, з яким згодні всі спостерігачі, так що або fetch_addперед, exchangeабо навпаки.

Якщо fetch_addдо exchangeцього, частина "випуску" fetch_addсинхронізується з частиною "придбати", exchangeі, таким чином, всі побічні ефекти set()повинні бути видимими для виконання коду check(), тому bar()не буде викликано.

В іншому випадку exchangeє раніше fetch_add, тоді fetch_addбуде бачити, 1а не дзвонити foo(). Отже, назвати і те, foo()і неможливо bar(). Чи правильно це міркування?


Варіант С

Використовуйте фіктивну атоміку, щоб ввести "краї", які запобігають катастрофі. Розглянемо наступний підхід:

void thread_a(){
  std::atomic<int> dummy1{};
  set();
  dummy1.store(13);
  if(!y.load()) foo();
}
void thread_b(){
  std::atomic<int> dummy2{};
  y.store(1);
  dummy2.load();
  if(!check()) bar();
}

Якщо ви думаєте, що проблема тут atomicлокальна, то уявіть, як перемістити їх до глобальної сфери, в наступних міркуваннях це не має для мене значення, і я навмисно написав код таким чином, щоб викрити, наскільки смішним це манекен1 і манекен2 повністю розділені.

Чому на Землі це може працювати? Ну, має бути якийсь єдиний загальний порядок, {dummy1.store(13), y.load(), y.store(1), dummy2.load()}який повинен відповідати програмним "краям":

  • dummy1.store(13) "в TO є раніше" y.load()
  • y.store(1) "в TO є раніше" dummy2.load()

(Сподіваємось, що магазин seq_cst + завантаження сподівається утворювати C ++ еквівалент повного бар'єру пам'яті, включаючи StoreLoad, як це робиться в asm для реальних ISA, включаючи навіть AArch64, де не потрібні окремі інструкції щодо бар'єру.)

Тепер ми маємо розглянути два випадки: або y.store(1)до, y.load()або після загального порядку.

Якщо y.store(1)до y.load()цього, foo()то не дзвонить, і ми в безпеці.

Якщо y.load()до цього y.store(1), то, поєднавши його з двома ребрами, які ми вже маємо в програмному порядку, виводимо, що:

  • dummy1.store(13) "в TO є раніше" dummy2.load()

Тепер, dummy1.store(13)це операція випуску, яка випускає ефекти set(), і dummy2.load()це операція придбання, тому check()слід бачити ефекти, set()і таким чином bar()не буде викликатися, і ми в безпеці.

Чи правильно тут думати, що check()побачать результати set()? Чи можу я поєднувати "ребра" різних видів ("програмний порядок", відомий як послідовно раніше, "загальний порядок", "до виходу", "після придбання") так? У мене є серйозні сумніви з цього приводу: правила C ++, схоже, говорять про "синхронізує-з" відносини між магазином і завантаженням на одному місці - тут такої ситуації немає.

Зауважте, що нас хвилює лише той випадок, коли, dumm1.storeяк відомо (за допомогою інших міркувань), це було dummy2.loadв загальному порядку seq_cst. Отже, якби вони отримували доступ до однієї і тієї ж змінної, навантаження побачило б збережене значення і синхронізувалося з ним.

(Обґрунтування перешкод для пам’яті / упорядкування перетворень для реалізацій, де атомні навантаження та сховища збираються принаймні в один бік бар'єри пам’яті (а операції seq_cst не можуть змінити порядок: наприклад, сховище seq_cst не може передати навантаження seq_cst) полягає в тому, що будь-які навантаження / магазини після, dummy2.loadбезумовно, стають видимими для інших потоків після y.store . І аналогічно для іншої теми , ... раніше y.load.)


Ви можете грати з моєю реалізацією параметрів A, B, C за адресою https://godbolt.org/z/u3dTa8


1
Модель пам'яті C ++ не має жодної концепції переупорядкування StoreLoad, лише Синхронізується - із тим, що відбувається раніше. (І UB на перегонах даних на неатомних об'єктах, на відміну від ASM для реального обладнання.) У всіх реальних реалізаціях, про які я знаю, std::atomic_thread_fence(std::memory_order_seq_cst)складається до повного бар'єру, але оскільки вся концепція є детальною інформацією про реалізацію, ви не знайдете будь-яка згадка про це в стандарті. (Модель пам'яті процесора зазвичай є визначені в термінах того , що reorerings допускаються по відношенню до послідовної послідовності , наприклад х86 сл-сСт + магазин буфер ж / експедирування.)
Пітер Кордес

@PeterCordes спасибі, я, можливо, не зрозумів у своєму написанні. Я хотів передати те, що ви написали у розділі «Варіант А». Я знаю, що в заголовку мого питання використовується слово "StoreLoad", і що "StoreLoad" - це поняття з зовсім іншого світу. Моя проблема - як відобразити цю концепцію на C ++. Або якщо це неможливо відобразити безпосередньо, тоді як досягти поставленої мною мети: запобігти foo()і bar()викликати обидва.
qbolec

1
Ви можете використовувати compare_exchange_*для виконання операції RMW на атомному булі, не змінюючи його значення (просто встановіть очікуване і нове на те саме значення).
mpoeter

1
@Fareanor і qbolec: atomic<bool>має exchangeі compare_exchange_weak. Останній може бути використаний для того, щоб зробити манекен RMW шляхом (спроби) CAS (істинного, істинного) або false, хибного. Він або відмовляється, або атомно замінює значення самим собою. (У x86-64 asm, ця хитрість lock cmpxchg16bполягає в тому, як ви робите гарантовані атомні 16-байтові навантаження; неефективні, але менш погані, ніж знімати окремий замок.)
Пітер Кордес,

1
@PeterCordes так, я знаю, що може статися, що ні, foo()і bar()не будуть викликані. Я не хотів залучати до багатьох елементів "реального світу" коду, щоб уникнути "ви думаєте, що у вас проблема X, але у вас є проблема Y". Але, якщо дійсно потрібно знати, що таке передісторія: set()це дійсно some_mutex_exit(), check()є try_enter_some_mutex(), yє "є якісь офіціанти", foo()це "вихід, не прокидаючи нікого", bar()це "чекати розбудови" ... Але я відмовляюся обговоримо цей дизайн тут - я не можу його реально змінити.
qbolec

Відповіді:


5

Варіанти А і В є дійсними рішеннями.

  • Варіант A: насправді не має значення, що означає перегородка seq-cst, стандарт C ++ чітко визначає, які гарантії він надає. Я виклав їх у цій публікації: Коли корисний паркан пам'яті_порядок_сек_cst?
  • Варіант В: так, ваші міркування правильні. Усі модифікації на якомусь об'єкті мають єдиний загальний порядок (порядок модифікації), тому ви можете використовувати це для синхронізації потоків та забезпечення видимості всіх побічних ефектів.

Однак, варіант C є НЕ дійсним! Співвідношення синхронізації з відношенням може бути встановлене лише операціями набуття / випуску на одному об'єкті . У вашому випадку у вас є два абсолютно різних об'єкта dummy1і непомітні об'єкти dummy2. Але вони не можуть бути використані для встановлення відносин, що трапляються раніше. Насправді, оскільки атомні змінні суто локальні (тобто їх коли-небудь торкається лише один потік), компілятор вільний їх видалити, грунтуючись на правилі як-якщо .

Оновлення

Варіант А:
Я припускаю , що set()і check()робити працюють на якому - то атомарного значення. Тоді у нас є така ситуація (-> позначає послідовно раніше ):

  • set()-> fence1(seq_cst)->y.load()
  • y.store(true)-> fence2(seq_cst)->check()

Тож ми можемо застосувати таке правило:

Для атомних операцій A і B на атомному об'єкті M , де A модифікує M і B приймає його значення, якщо є memory_order_seq_cstогорожі X і Y такі, що A секвенується перед X , Y секвенується перед B , а X передує Y в S , то B зауважує або ефекти A, або більш пізню модифікацію M у порядку модифікації.

Тобто, або check()бачить це значення, яке зберігається set, або y.load()бачить записане значення y.store()(операції над yнавіть можна використовувати memory_order_relaxed).

Варіант C: C ++ 17 стандартних станів [32.4.3, P1347]:

Існує єдиний загальний порядок S для всіх memory_order_seq_cstоперацій, що відповідає порядку "відбувається перед" та замовленням на модифікацію для всіх уражених місць [...]

Тут важливе слово - «послідовність». Це означає , що якщо операція відбувається, перед операцією Б , то повинно передувати B в S . Однак, логічний висновок є односторонній вулицею, тому ми не можемо робити висновок зворотним: тільки тому , що деякі операції C передує операцію D в S не означають , що C відбувається до D .

Зокрема, дві операції seq-cst над двома окремими об'єктами не можуть бути використані для встановлення ставлення до того, що трапляється перед відношенням, навіть якщо операції повністю впорядковані в S. Якщо ви хочете замовити операції на окремих об'єктах, вам потрібно звернутися до seq-cst -огорожі (див. Варіант А).


Не очевидно, що варіант C недійсний. Операції seq-cst навіть на приватних об'єктах все ще можуть певною мірою замовляти інші операції. Погоджено, що синхронізації немає, але нам не байдуже, який з футів або смужок працює (або, мабуть, ні), лише те, що вони обидва не працюють. Я думаю, що це дає нам відношення послідовності перед послідовністю та загальний порядок операцій seq-cst (які повинні існувати).
Пітер Кордес

Дякую @mpoeter. Скажіть, будь ласка, варіант Варіант А. Яка з трьох куль у Вашій відповіді застосовується тут? IIUC, якщо y.load()не бачить ефекту y.store(1), тоді ми можемо довести з правил, що в S, atomic_thread_fencethread_a є раніше, ніж atomic_thread_fencethread_b. Те, що я не бачу, - це як зробити висновок про set()видимість побічних ефектів check().
qbolec

1
@qbolec: Я оновив свою відповідь більш детально про варіант А.
mpoeter

1
Так, локальна операція seq-cst все ще буде частиною єдиного загального порядку S для всіх операцій seq-cst. Але S «тільки» в відповідно до трапляється, перш ніж замовляти і модифікації замовлень , тобто, якщо відбувається, перш за , ніж В , то повинно передувати B в S . Але протилежне не гарантується, тобто, тільки тому , що A передує B в S , ми можемо не виводять , що відбувається, перш за , ніж B .
mpoeter

1
Ну, якщо припустити , що setі checkможе безпечно виконуватися паралельно, я б , ймовірно , піти з опцією А, особливо якщо це продуктивність критично, так як це дозволяє уникнути розбіжностей по загальній змінної y.
mpoeter

1

У першому прикладі y.load()читання 0 не означає, що y.load()відбувається раніше y.store(1).

Однак це означає, що раніше в єдиному загальному порядку відбувається правило, що навантаження seq_cst повертає або значення останнього сховища seq_cst у загальному порядку, або значення деякого не-seq_cst-магазину, що не відбувається раніше його (якого в даному випадку не існує). Так якби y.store(1)раніше, ніж y.load()у загальному порядку, y.load()повернув би 1.

Доказ все ще є правильним, оскільки єдине загальне замовлення не має циклу.

Як щодо цього рішення?

std::atomic<int> x2{0},y{0};

void thread_a(){
  set();
  x2.store(1);
  if(!y.load()) foo();
}

void thread_b(){
  y.store(1);
  if(!x2.load()) bar();
}

Проблема ОП полягає в тому, що я не маю контролю над "X" - він знаходиться за макросами обгортки або чимось іншим і може не бути seq-cst store / load. Я оновив питання, щоб краще виділити це.
Пітер Кордес

@PeterCordes Ідея полягала в тому, щоб створити ще один "х", над яким він має контроль. Я перейменую його на "x2" у своїй відповіді, щоб зробити його зрозумілішим. Я впевнений, що я пропускаю якусь вимогу, але якщо єдиною вимогою є переконання, що foo () і bar () не викликаються обома, то це задовольняє це.
Tomek Czajka

Так, if(false) foo();але я думаю, що ОП цього не хоче: P Цікавий момент, але я думаю, що ОП хоче, щоб умовні дзвінки базувалися на визначених ними умовах!
Пітер Кордес

1
Привіт @TomekCzajka, дякую, що знайшов час, щоб запропонувати нове рішення. У моєму конкретному випадку це не спрацювало, оскільки воно не містить важливих побічних ефектів check()(див. Мій коментар до мого запитання щодо значення реального світу set,check,foo,bar). Я думаю, що це може працювати if(!x2.load()){ if(check())x2.store(0); else bar(); }замість цього.
qbolec

1

@mpoeter пояснив, чому параметри A і B безпечні.

На практиці щодо реальних реалізацій, я вважаю, що варіант A потрібен лише std::atomic_thread_fence(std::memory_order_seq_cst)для теми A, а не B.

магазини seq-cst на практиці містять повний бар'єр пам'яті, або AAArch64 принаймні не може змінити порядок з пізнішими навантаженнями або завантаженнями seq_cst ( stlrпослідовне випуску повинно зливатися з буфера зберігання, перш ніж ldarможна прочитати з кешу).

C ++ -> відображення asm має вибір вибору витрат на злив буфера магазину в атомні сховища або на атомні навантаження. Раціональним вибором для реальної реалізації є зробити атомні навантаження дешевими, тому магазини seq_cst містять повний бар'єр (включаючи StoreLoad). Хоча seq_cst навантаження такі ж, як і навантаження на більшості.

(Але не POWER; навіть для навантажень потрібна великовагова синхронізація = повний бар'єр, щоб зупинити пересилання передачі з інших потоків SMT на тому ж ядрі, що може призвести до переупорядкування IRIW, оскільки seq_cst вимагає, щоб усі потоки мали змогу узгодити порядок всі seq_cst ops. Чи будуть два атомних запису в різні місця в різних потоках завжди бачитись в одному порядку іншими потоками? )

(Звичайно, для формальної гарантії безпеки нам потрібен паркан для того, щоб сприяти придбанню / випуску набору () -> check () в seq_cst синхронізується-з. Було б також працювати для невимушеного набору, я думаю, але розслаблена перевірка може змінити порядок із смужкою з POV інших потоків.)


Я думаю, що реальна проблема з Варіантом С полягає в тому, що це залежить від гіпотетичного спостерігача, який міг би синхронізувати yоперації з манекенами. Таким чином, ми очікуємо, що компілятор збереже це впорядкування під час створення asm для ISA на основі бар'єру.

Це буде справедливо на практиці щодо реальних ISA; обидва потоки включають повний бар'єр або еквівалент, і компілятори не (поки) не оптимізують атомію. Але, звичайно, "компіляція до ISA на основі бар'єру" не є частиною стандарту ISO C ++. Когерентний спільний кеш - це гіпотетичний спостерігач, який існує для міркувань про asm, але не для міркувань ISO C ++.

Щоб варіант C працював, нам потрібне замовлення на зразок dummy1.store(13);// y.load()/ set();(як видно з теми B) для порушення деяких правил ISO C ++ .

Потік, у якому запущені ці оператори, повинен вести себе так, ніби set() виконується спочатку (через Послідовність раніше). Це добре, впорядкування пам’яті під час виконання та / або упорядкування часу операцій з упорядкуванням може все-таки зробити це.

Два опції seq_cst d1=13і yвідповідають послідовному послідовності раніше (програмний порядок). set()не бере участі у необхідному для існування глобальному порядку для seq_cst ops, оскільки це не seq_cst.

Гілка B НЕ синхронізує-с dummy1.store так не відбувається, перш за , ніж вимоги по setвідношенню до d1=13застосовується , незважаючи на те, що призначення операція релізу.

Я не бачу інших можливих порушень правил; Я не можу знайти тут нічого, що повинно відповідати попередньому setпослідовності d1=13.

Міркування "dummy1.store release set ()" є вадою. Це впорядкування застосовується лише до реального спостерігача, який синхронізується з ним, або в ASM. Як відповів @mpoeter, існування загального замовлення seq_cst не створює і не передбачає того, що відбувається раніше, ніж стосунки, і це єдине, що формально гарантує замовлення поза seq_cst.

Будь-який "звичайний" процесор з когерентним спільним кешем, де це переупорядкування справді може відбутися під час виконання, не здається правдоподібним. (Але якщо компілятор міг би видалити, dummy1і dummy2тоді явно виникли б проблеми, і я думаю, що це дозволено стандартом.)

Але оскільки модель пам’яті C ++ не визначається з точки зору буфера зберігання, спільного когерентного кешу чи тестування дозволеного переупорядкування, речі, необхідні з розумом, формально не вимагаються правилами C ++. Це, мабуть, навмисне, щоб дозволити оптимізувати навіть змінні seq_cst, які виявляються приватними потоками. (Звичайно, поточні компілятори цього не роблять, або будь-яка інша оптимізація атомних об'єктів.)

Реалізація, де один потік дійсно міг бачитись set()останнім, а інший бачити set()перші звуки неправдоподібними. Навіть влада не могла цього зробити; як seq_cst завантаження, так і магазин містять повні бар'єри для POWER. (Я підказав у коментарях, що переупорядкування IRIW може бути тут доречним; правила acq / rel C ++ є досить слабкими для їх задоволення, але повна відсутність гарантій за межами синхронізацій - із ситуаціями, що трапляються раніше - значно слабша, ніж будь-яка HW. )

C ++ нічого не гарантує для non-seq_cst, якщо насправді немає спостерігача, і то лише для цього спостерігача. Без одного ми на території котячих Шрьодінгера. Або, якщо в лісі падають два дерева, чи впало одне перед іншим? (Якщо це великий ліс, загальна відносність говорить, що це залежить від спостерігача і що немає універсальної концепції одночасності.)


@mpoeter припустив, що компілятор може навіть видалити фіктивне завантаження та зберігати операції навіть на об’єктах seq_cst.

Я думаю, що це може бути правильним, коли вони можуть довести, що нічого не може синхронізуватися з операцією. наприклад компілятор, який може побачити, що dummy2не виходить з функції, можливо, може видалити це seq_cst навантаження.

Це має щонайменше один наслідк у реальному світі: якби компілювати для AArch64, це дозволило б попередньому магазину seq_cst змінити порядок на практиці з пізнішими розслабленими операціями, що не було б можливим, коли магазин seq_cst + навантаження злив буфер магазину перед будь-яким пізніші навантаження можуть виконуватись.

Звичайно, поточні компілятори взагалі не оптимізують атоміку, навіть якщо ISO C ++ не забороняє цього; це невирішена проблема для комітету зі стандартів.

Це дозволено, я думаю, оскільки модель пам'яті C ++ не має неявного спостерігача або вимоги, щоб усі потоки узгоджувались щодо замовлення. Він дає деякі гарантії, засновані на когерентних кешах, але він не вимагає одночасної видимості для всіх потоків.


Приємний підсумок! Я погоджуюсь, що на практиці цього, ймовірно, буде достатньо, якби тільки нитка A мала послідовну огорожу. Однак, виходячи зі стандарту C ++, ми не мали б необхідної гарантії того, що ми бачимо останнє значення set(), тому я все одно використовую огорожу в нитці B. Я припускаю, що розслаблений магазин із парканом seq-cst у будь-якому разі генерує майже той самий код, що і seq-cst-store.
mpoeter

@mpoeter: Так, я говорив лише на практиці, а не формально. Додано примітку в кінці цього розділу. І так, на практиці в більшості ISA я думаю, що магазин seq_cst, як правило, просто звичайний магазин (розслаблений) + бар'єр. Чи ні; на POWER магазин seq-cst робить (важкий) sync перед магазином, нічого після цього. godbolt.org/z/mAr72P Але для завантаження seq-cst потрібні певні бар'єри з обох сторін.
Пітер Кордес

1

у стандарті ISO std :: mutex гарантується лише замовлення на придбання та випуск, а не seq_cst.

Але нічого не гарантується як "seq_cst замовлення", як seq_cstце не є властивістю жодної операції.

seq_cstє гарантією всіх операцій даної реалізації std::atomicабо альтернативного атомного класу. Таким чином, ваше запитання є невиразним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.