У меня есть два потока: один обновляет int, а другой читает его. Это статистическое значение, при котором порядок чтения и записи не имеет значения.
У меня вопрос: нужно ли мне все равно синхронизировать доступ к этому многобайтовому значению? Или, другими словами, часть записи может быть завершена и прервана, а затем произойдет чтение.
Например, подумайте о значении = 0x0000FFFF, которое получает увеличенное значение 0x00010000.
Есть ли время, когда значение выглядит как 0x0001FFFF, о котором мне следует беспокоиться? Конечно, чем крупнее тип, тем больше вероятность того, что произойдет что-то подобное.
Я всегда синхронизировал эти типы доступа, но мне было любопытно, что думает сообщество.
Интересное чтиво по теме: channel9.msdn.com/Shows/Going+Deep/…
Специально для =
: stackoverflow.com/questions/8290768/…
Нет, это не так (по крайней мере, вы не можете предположить, что это так). Сказав это, есть несколько уловок, чтобы сделать это атомарно, но они обычно не переносимы (см. Сравнить и обменять).
Да, вам нужно синхронизировать доступы. В C++ 0x это будет гонка данных и неопределенное поведение. С потоками POSIX это уже неопределенное поведение.
На практике вы можете получить неверные значения, если тип данных больше, чем исходный размер слова. Кроме того, другой поток может никогда не увидеть записанное значение из-за оптимизации, перемещающей чтение и / или запись.
Вы должны синхронизировать, но на некоторых архитектурах есть эффективные способы сделать это.
Лучше всего использовать подпрограммы (возможно, замаскированные за макросами), чтобы можно было условно заменить реализации на специфичные для платформы.
В ядре Linux уже есть часть этого кода.
ЕСЛИ вы читаете / записываете 4-байтовое значение, И оно выровнено по DWORD в памяти, И вы работаете на архитектуре I32, ТО чтение и запись являются атомарными.
Где в руководствах разработчика программного обеспечения архитектуры Intel это сказано?
@DanielTrebbien: возможно, см. stackoverflow.com/questions/5002046/…
Мальчик, что за вопрос. Ответ на который:
Yes, no, hmmm, well, it depends
Все сводится к архитектуре системы. На IA32 правильно выровненный адрес будет атомарной операцией. Невыровненная запись может быть атомарной, это зависит от используемой системы кэширования. Если память находится в одной строке кэша L1, то она атомарна, в противном случае - нет. Ширина шины между ЦП и ОЗУ может повлиять на атомарный характер: правильно выровненная 16-битная запись на 8086 была атомарной, тогда как такая же запись на 8088 не была, потому что 8088 имел только 8-битную шину, тогда как 8086 имел 16-битная шина.
Кроме того, если вы используете C / C++, не забудьте пометить общее значение как изменчивое, иначе оптимизатор будет думать, что переменная никогда не обновляется в одном из ваших потоков.
Ключевое слово volatile бесполезно в многопоточных программах stackoverflow.com/questions/2484980/…
@IngeHenriksen: Меня не убедила эта ссылка.
другой источник, но, к сожалению, очень старый (он предшествует std :: atomic): web.archive.org/web/20190219170904/https://software.intel.co м /…
Согласен со многими и особенно с Джейсон. В Windows можно было бы использовать InterlockedAdd и его друзей.
Сначала можно подумать, что чтение и запись размера машинной машины являются атомарными, но есть ряд проблем, которые необходимо решить, включая согласованность кеша между процессорами / ядрами. Используйте атомарные операции, такие как Interlocked * в Windows и аналогичные в Linux. C++ 0x будет иметь «атомарный» шаблон, чтобы обернуть их в красивый и кроссплатформенный интерфейс. На данный момент, если вы используете уровень абстракции платформы, он может предоставлять эти функции. ТУЗ делает, см. Шаблон класса ACE_Atomic_Op.
Документ ACE_Atomic_Op перемещен - теперь его можно найти по адресу dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inl
Чтобы повторить то, что все говорили наверху, язык, предшествующий C++ 0x, не может ничего гарантировать в отношении доступа к общей памяти из нескольких потоков. Любые гарантии будут зависеть от компилятора.
Помимо проблемы с кешем, упомянутой выше ...
Если вы перенесете код на процессор с меньшим размером регистра, он больше не будет атомарным.
ИМО, проблемы с потоками слишком сложны, чтобы рисковать.
В Windows Interlocked *** Exchange *** Add гарантированно будет атомарным.
Единственный переносимый способ - использовать тип sig_atomic_t, определенный в заголовке signal.h для вашего компилятора. В большинстве реализаций C и C++ это int. Затем объявите вашу переменную как «volatile sig_atomic_t».
volatile не делает то, что вы думаете stackoverflow.com/questions/2484980/…
Возьмем этот пример
int x;
x++;
x=x+5;
Предполагается, что первый оператор является атомарным, поскольку он преобразуется в одну директиву сборки INC, которая занимает один цикл ЦП. Однако второе присваивание требует нескольких операций, так что это явно не атомарная операция.
Другой, например,
x=5;
Опять же, вам нужно дизассемблировать код, чтобы увидеть, что именно здесь происходит.
Но компилятор мог оптимизировать его до x+=6
.
tc, Я думаю, что в тот момент, когда вы используете константу (например, 6), инструкция не будет выполнена за один машинный цикл. Попытайтесь увидеть набор инструкций x + = 6 по сравнению с x ++
Некоторые люди думают, что ++ c является атомарным, но следят за созданной сборкой. Например, с 'gcc -S':
movl cpt.1586(%rip), %eax
addl , %eax
movl %eax, cpt.1586(%rip)
Чтобы увеличить int, компилятор сначала загружает его в регистр и сохраняет обратно в память. Это не атомарно.
Это не проблема, если в переменную записывает только один поток, поскольку разрывов нет.
Однозначно НЕТ!
Этот ответ от нашего высшего авторитета в области C++, М. Boost:
Не гарантируется, что операции с «обычными» переменными будут атомарными.
эта ссылка говорит только о том, что операция arithmetic
, которая состоит из последовательности чтения-обновления-записи для «обычных» переменных, не является атомарной, а не является ли операция read
или write
для «обычных» переменных атомарной или нет.
Действительно? Мне было бы все равно, что думает сообщество. Мне было бы наплевать, каковы факты :)