Я прослухав і прочитав декілька статей, бесід та запитань про поточний потік std::atomic
, і хотів би бути впевненим, що я це добре зрозумів. Оскільки я все ще трохи заплутаний у кеш-рядку запису видимості через можливі затримки протоколів когерентності кеш-пам'яті MESI (або похідних), зберігання буферів, недійсних черг тощо.
Я прочитав, що x86 має більш сильну модель пам'яті, і якщо затримка відключення кешу затримана, x86 може відновити розпочаті операції. Але мене зараз цікавить лише те, що я повинен вважати програмістом C ++ незалежно від платформи.
[T1: thread1 T2: thread2 V1: загальна атомна змінна]
Я розумію, що std :: atomic гарантує це,
(1) За змінною не відбувається перегонів даних (завдяки виключному доступу до рядка кешу).
(2) Залежно від того, якою пам’яткою пам’яті ми користуємось, вона гарантує (з бар’єрами), що буде послідовна послідовність (перед бар’єром, після бар’єру чи обох).
(3) Після атомного запису (V1) на T1 атомна RMW (V1) на T2 буде когерентною (її кеш-рядок буде оновлено записуваним значенням на T1).
Але як згадується праймер когерентності кешу ,
Слід сказати, що за замовчуванням завантаження можуть отримувати несвіжі дані (якщо відповідна запит щодо недійсності сидів у черзі недійсності)
Отже, чи правильно таке?
(4) std::atomic
НЕ гарантує, що T2 не буде читати «черстве» значення на атомному зчитуванні (V) після атомного запису (V) на T1.
Питання, якщо (4) вірно: якщо атомний запис на T1 приводить до недійсності лінії кеша незалежно від затримки, чому T2 чекає, що вимкнення буде ефективним, коли атомна операція RMW не зчитується?
Питання, якщо (4) помиляється: тоді, коли нитка може прочитати значення "несвіже" та "це видно" у виконанні?
Я дуже ціную ваші відповіді
Оновлення 1
Тож, здається, тоді я помилявся (3). Уявіть таке перемежування для початкового V1 = 0:
T1: W(1)
T2: R(0) M(++) W(1)
Навіть незважаючи на те, що RMW T2 гарантовано відбудеться повністю після W (1), в цьому випадку він все ще може прочитати «несвіже» значення (я помилявся). Відповідно до цього, атомний не гарантує повну когерентність кешу, лише послідовну послідовність.
Оновлення 2
(5) Тепер уявіть цей приклад (x = y = 0 і є атомним):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
згідно з тим, про що ми говорили, побачення "msg", що відображається на екрані, не дасть нам інформації, крім того, що T2 був виконаний після T1. Тож може статися будь-яке з наступних страт:
- T1 <T3 <T2
- T1 <T2 <T3 (де T3 бачить x = 1, але не y = 1)
це так?
(6) Якщо потік завжди може читати "несвіжі" значення, що буде, якби ми взяли типовий сценарій "опублікувати", але замість того, щоб сигналізувати про те, що деякі дані готові, ми робимо навпаки (видаляємо дані)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
де T2 все ще буде використовувати видалений ptr, поки не побачить, що is_enabled не відповідає дійсності.
(7) Крім того, той факт, що потоки можуть читати «несвіжі» значення, означає, що мьютекс не може бути реалізований лише з одним атомним правом без блокування? Для цього знадобиться синхронізуючий механізм між потоками. Чи потрібна буде атомна, що може бути заблокована?