Чи читає та пише С ++ атомний?


81

У мене є два потоки, один оновлює int, а другий читає його. Це статистичне значення, коли порядок читання та запису не має значення.

Моє запитання: чи потрібно мені все одно синхронізувати доступ до цього багатобайтового значення? Або, інакше кажучи, частина записування може бути завершена і перервана, і тоді читання відбудеться.

Наприклад, подумайте про значення = 0x0000FFFF, яке отримує збільшене значення 0x00010000.

Чи є момент, коли значення виглядає як 0x0001FFFF, про що я повинен хвилюватися? Звичайно, чим більший тип, тим більше можливо щось подібне.

Я завжди синхронізував такі типи доступу, але мені було цікаво, що думає спільнота.


5
Справді? Мені було б все одно, що думає громада. Мені було б
цікаво,

1
Цікаве читання на тему: channel9.msdn.com/Shows/Going+Deep/…
ere3

Відповіді:


47

Спочатку можна подумати, що читання та запис власного розміру машини є атомарними, але є ряд проблем, з якими слід вирішити питання, включаючи когерентність кешу між процесорами / ядрами. Використовуйте атомні операції, такі як Interlocked * у Windows та еквівалентні в Linux. C ++ 0x матиме "атомний" шаблон, щоб обернути їх у приємний та крос-платформний інтерфейс. Наразі, якщо ви використовуєте рівень абстракції платформи, він може надавати ці функції. ACE робить, див. Шаблон класу ACE_Atomic_Op .


Документ ACE_Atomic_Op переміщений - тепер його можна знайти за адресою dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inl
Байрон,

63

Хлопче, яке питання. Відповідь на це:

Так, ні, мммм, ну це залежить

Все зводиться до архітектури системи. На IA32 правильно вирівняна адреса буде атомною операцією. Невирівняні записи можуть бути атомними, це залежить від використовуваної системи кешування. Якщо пам'ять знаходиться в межах одного рядка кешу L1, тоді вона є атомною, інакше ні. Ширина шини між процесором і оперативною пам'яттю може вплинути на атомну природу: правильно вирівняна 16-бітна запис на 8086 була атомною, тоді як така ж запис на 8088 не була, оскільки 8088 мала лише 8-бітну шину, тоді як 8086 мала 16-розрядна шина.

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


23
Летючі ключове слово не є корисним в багатопоточних програмах stackoverflow.com/questions/2484980 / ...

5
@IngeHenriksen: Я не переконаний цим посиланням.
Skizz

ще одне джерело, але, на жаль, дуже давнє (воно передує std :: atomic): web.archive.org/web/20190219170904/https://software.intel.com/…
Макс

11

ЯКЩО ви читаєте / пишете 4-байтове значення І воно вирівнюється за DWORD в пам’яті І ви працюєте на архітектурі I32, ПОТІМ читання та запис є атомними.


2
Де це сказано в керівництві розробника програмного забезпечення для архітектури Intel?
Даніель Требб'єн

2
@DanielTrebbien: можливо побачити stackoverflow.com/questions/5002046 / ...
sehe

9

Так, вам потрібно синхронізувати доступ. У C ++ 0x це буде гонка даних та невизначена поведінка. З потоками POSIX це вже невизначена поведінка.

На практиці ви можете отримати неправильні значення, якщо тип даних перевищує розмір рідного слова. Крім того, інший потік може ніколи не бачити написане значення внаслідок оптимізації переміщення читання та / або запису.


3

Ви повинні синхронізуватись, але на певних архітектурах існують ефективні способи зробити це.

Найкраще використовувати підпрограми (можливо, замасковані за макросами), щоб ви могли умовно замінити реалізації конкретними для платформи.

Ядро Linux вже має частину цього коду.



2

Щоб повторити те, що всі говорили нагорі, мова pre-C ++ 0x не може гарантувати нічого про доступ до спільної пам'яті з декількох потоків. Будь-які гарантії залежать від компілятора.



0

Я згоден з багатьма і особливо з Джейсоном . На вікнах, швидше за все, можна було б використовувати InterlockedAdd та його друзів.


0

Повідомлення про проблему з кешем, згадану вище ...

Якщо ви перенесете код на процесор із меншим розміром реєстру, він більше не буде атомним.

ІМО, потокові проблеми занадто тернисті, щоб ризикувати.


0

Єдиний портативний спосіб - використовувати тип sig_atomic_t, визначений у заголовку signal.h для вашого компілятора. У більшості реалізацій C і C ++ це int. Потім оголосіть свою змінну "volatile sig_atomic_t."


volatile не робить того, що, на вашу думку, робить stackoverflow.com/questions/2484980/…
Сем Міллер

0

Візьмемо цей приклад

int x;
x++;
x=x+5;

Перше твердження вважається атомним, оскільки воно перекладається в єдину директиву збірки INC, яка приймає один цикл процесора. Однак друге призначення вимагає декількох операцій, тому це явно не атомна операція.

Інший, наприклад,

x=5;

Знову ж таки, вам доведеться розібрати код, щоб побачити, що саме тут відбувається.


2
Але компілятор міг би його оптимізувати x+=6.
тк.

0

tc, я думаю, що в той момент, коли ви використовуєте константу (наприклад, 6), інструкція не буде виконана за один машинний цикл. Спробуйте побачити набір інструкцій x + = 6 порівняно з x ++


0

Деякі люди думають, що ++ c є атомним, але оглядають згенеровану збірку. Наприклад, з 'gcc -S':

movl    cpt.1586(%rip), %eax
addl    $1, %eax
movl    %eax, cpt.1586(%rip)

Для збільшення int компілятор спочатку завантажує його в регістр і зберігає назад у пам’яті. Це не атомно.


1
Це не проблема, якщо до змінної пише лише один потік, оскільки розрив не відбувається.
Бен Войгт,

0

Однозначно НІ! Ця відповідь нашого найвищого авторитету на C ++, М. Boost:
Операції над "звичайними" змінними не гарантуються атомними.


2
це посилання говорить лише про те, що arithmeticоперація, яка складається з послідовності читання-оновлення-запис для "звичайних" змінних, не є атомними, а також про те, readчи є writeоперація над "звичайними" змінними атомною чи ні.
D3Hunter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.