Что такое атомарность в программировании
Перейти к содержимому

Что такое атомарность в программировании

  • автор:

Атомарность

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

Атомарная операция открыта влиянию только одного потока.

Атомарность бывает аппаратная (когда непрерывность обеспечивается аппаратурой) и программной, когда используются специальные средства межпрограммного взаимодействия (мьютекс, семафор). По своей сути программные средства обеспечения атомарности представляют собой два этапа — блокировка ресурса и выполнение самой операции. Блокировка представляет собой атомарную операцию, которая либо успешна, либо возвращает сообщение о занятости.

См. также

  • Сериализуемость
  • Линеаризуемость
  • Последовательная консистентность

Wikimedia Foundation . 2010 .

Синонимы:

  • Сражение при Гренгаме
  • Улица Щипок

Смотреть что такое «Атомарность» в других словарях:

  • атомарность — элементарность; нецелостность, дробность Словарь русских синонимов. атомарность сущ., кол во синонимов: 3 • дробность (9) • … Словарь синонимов
  • атомарность — АТОМАРНЫЙ, ая, ое. Толковый словарь Ожегова. С.И. Ожегов, Н.Ю. Шведова. 1949 1992 … Толковый словарь Ожегова
  • атомарность — Свойство транзакций (см. ACID). Транзакция рассматривается как единая логическая единица, все изменения в базе данных, произведенные во время ее выполнения, или сохраняются целиком, или полностью откатываются. [http://www.morepc.ru/dict/]… … Справочник технического переводчика
  • атомарность, непротиворечивость, изолированность, долговечность — Свойства, присущие транзакции. атомарность Свойство атомарности (atomicity) обозначает, что входящие в транзакцию операции выступают вместе как неделимая единица работы, т.е. либо все операции успешно завершаются, либо отменяются. Это делает… … Справочник технического переводчика
  • атомарность — сложность … Словарь антонимов
  • атомарность — Syn: элементарность … Тезаурус русской деловой лексики
  • ароматность — атомарность … Краткий словарь анаграмм
  • ACID — У этого термина существуют и другие значения, см. Acid. В информатике акроним ACID описывает требования к транзакционной системе (например, к СУБД), обеспечивающие наиболее надёжную и предсказуемую её работу. Требования ACID были в основном… … Википедия
  • Атомарные операции — Атомарные операции операции, выполняющиеся как единое целое либо не выполняющиеся вовсе. Атомарность операций имеет особое значение в многопроцессорных компьютерах (и многозадачных операционных системах), так как доступ к неразделяемым… … Википедия
  • Атомарная операция — Атомарные операции операции, выполняющиеся как единое целое либо не выполняющиеся вовсе. Атомарность операций имеет особое значение в многопроцессорных компьютерах (и многозадачных операционных системах), так как доступ к неразделяемым… … Википедия
  • Обратная связь: Техподдержка, Реклама на сайте
  • �� Путешествия

Экспорт словарей на сайты, сделанные на PHP,
WordPress, MODx.

  • Пометить текст и поделитьсяИскать в этом же словареИскать синонимы
  • Искать во всех словарях
  • Искать в переводах
  • Искать в ИнтернетеИскать в этой же категории

Атомарные операции в языке С: определение, суть и функции

Lorem ipsum dolor

Атомарные операции имеют важное значение в программировании. Атомарные операции — это такие операции, которые выполняются за один раз либо вообще не выполняются. То ест ь т акая операция не может быть выполненной наполовину или прерван ной в процессе выполнения.

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

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

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

Отличной реализацией атомарной операции является запись данных в массив. Например:

public class CuriousityExample

public volatile int[] myarray;

public void nonAtomicExample()

myarray = new int[1];

myarray[0] = 1;

>

public void probablyAtomicExample()

myarray = new int[] < 1 >;

>

>

На примере выше видно, что , когда мы используем метод «nonAtomicExample», тогда присутствует вероятность, что поток отправит запрос к «myarray[0]», пока тот не инициализировался ; из-за этого может возникнуть непредвиденный результат. Когда мы используем метод «probablyAtomicExample» , такого не случится, потому что массив сначала заполняется, а потом уже к нему отправляется запрос. Из-за этого существует определение, что атомарные операции видны всем потокам, которые участвуют в процессе.

Атомарные операции в С и их особенности

  1. Приложения, где происходят банковские операции. Финансовые операции должны происходить непрерывно и до конца. Нельзя , чтобы оплата или денежный перевод окончили работу на по лп ути и не выполнились до конца.
  2. Приложения для покупки билетов. Когда билет заказывается в онлайн-режиме с выбором места и стоимости, нельзя допустить, чтобы такие операции проходили не до конца.
  3. Многопоточные веб-приложения. В таких приложениях сервер, который разбирает HTTP-запросы , должен разбирать их в атомарном режиме. В другом случа е м ожет возникнуть ситуация, когда ряд запросов потеряется. О т этого пострадает функциональность приложения.

Заключение

Атомарные операции в С и других языках программирования всегда соотносились с понятием «неделимост и ». Так было уже не один десяток лет , о днако заговорили об атомарности с развитием многопоточного или параллельного программирования , п отому что разработчики стали различать ситуации, когда несколько параллельных потоков взаимодействуют с одними и теми же данными. В результате такого воздействия часть данных видоизменя е тся, а другая часть окончательно теряется. Чтобы решить такую проблему , было принято решени е выделять наиболее важные потоки, которые должны исполняться до конца, невзирая на работу других потоков. Этим и занимаются атомарные операции.

Мы будем очень благодарны

если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.

Простое руководство по атомарности в C++

Фото Dan Meyers на Unsplash

Часто возникает путаница с тем, что же понимается в компьютерных науках под «атомарностью». Как правило, атомарность – это свойство процесса, означающее, что он совершается за один шаг или операцию. Но в языке C++ атомарность определяется гораздо более специфичным образом. На самом деле, при использовании std::atomic с классами и типами еще не гарантируется, что весь код будет подлинно атомарным. Хотя, атомарные типы и входят в состав языка C++, сами атомарные операции должны поддерживаться на уровне того аппаратного обеспечения, на котором работает программа. Эта статья – простое руководство, помогающее понять, что же представляет собой атомарность в C++.

Типы

В C++ в шаблонный класс std::atomic<> можно обертывать и многие другие типы, что способствует атомарным операциям над соответствующим типом. Этот шаблон ни в коем случае не гарантирует, что все операции на самом деле получатся атомарными. Если какие-либо атомарные операции не поддерживаются задействованным CPU, то компилятор прибегнет к резервным вариантам на основе мьютексов. Хорошо, что есть полезная функция и гарантированный булев член атомарных типов – при помощи этих вещей можно проверить, поддерживает CPU атомарные операции над типами или нет.

#include #include int main() < printf("is lock free? %s\n", std::atomic::is_always_lock_free ? "true" : "false"); std::atomic a(3); printf("is this lock free? %s\n", a.is_lock_free() ? "true" : "false"); return 0; >

В вышеприведенном коде также подчеркивается еще один важный факт об атомарности: атомарны только операции, но не типы и не данные. Число int ничем не отличается от std::atomic в том смысле, какие данные оно представляет. Кроме того, типы std::atomic<> устроены так, что только атомарные операции предназначены для работы с теми данными, что представлены этим типом. Таким образом, атомарные и неатомарные операции никогда не перемешиваются.

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

Простейшими атомарными операциями являются загрузка и сохранение. Хотя, вы можете буквально представлять операцию «хранение значения в переменной», на самом деле, в атомарном хранилище данных происходит нечто иное. Во-первых, не забывайте, что цель атомарности – обеспечить возможность, чтобы много потоков могли изменять одни и те же данные, и это было потокобезопасно. Чтобы такое было возможно, эти данные должны существовать в разделяемой памяти или в кэше. Следовательно, при атомарной загрузке данные переносятся из разделяемой памяти либо в регистр, либо в участок памяти, специфичный для конкретного потока – в зависимости от архитектуры процессора. При атомарном сохранении данные атомарно записываются в разделяемую память.

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

Атомарность как момент времени

Возьмем переменную a, имеющую значение 3 в момент t1. Если атомарная загрузка применена в момент t1, то в результате этой операции загрузки будет получено значение 3. Однако, если загрузка произойдет на миг позже, в момент t2, то в результате может быть получено не 3, а другое значение, просто потому, что в момент t1 могла произойти другая операция.

Допустим, переменная a инициализируется в значении 1, так, что представление a до того, как над ней совершены какие-либо операции, должно быть равно 1. Если к переменной a применяется операция сохранения, а затем операция загрузки, то нет никакой гарантии, что по результатам второй операции будет извлечено именно то значение, которое было сохранено в переменной в результате первой операции. Дело в том, что сохранение и загрузка происходят в два разных момента времени. А эмпирически из опыта работы с атомарными операциями известно, что между двумя моментами времени может быть осуществлено практически бесконечное количество операций.

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

#include #include #include static std::atomic foobar(8); static std::atomic start(false); int main() < std::thread t1 = std::thread([]< int records[10]; while (!start.load()); for (size_t i = 0; i < 10; ++i) < foobar.store(3); records[i] = foobar.load(); >for (size_t j = 0; j < 10; ++j) < printf("t1 %zu - %d\n", j, records[j]); >>); std::thread t2 = std::thread([] < int records[10]; while (!start.load()); for (size_t i = 0; i < 10; ++i) < foobar.store(6); records[i] = foobar.load(); >for (size_t j = 0; j < 10; ++j) < printf("t2 %zu - %d\n", j, records[j]); >>); start.store(true); t1.join(); t2.join(); return 0;

Если вы скомпилируете и выполните эту программу, то можете получить примерно следующий результат:

t1 0 - 3 t1 1 - 3 t1 2 - 3 t1 3 - 3 t1 4 - 3 t1 5 - 3 t1 6 - 3 t1 7 - 3 t1 8 - 3 t1 9 - 3 t2 0 - 6 t2 1 - 6 t2 2 - 6 t2 3 - 6 t2 4 - 6 t2 5 - 6 t2 6 - 6 t2 7 - 6 t2 8 - 6 t2 9 - 6

Он может показаться странным – ведь, судя по выводу, каждый из потоков был в состоянии загрузить именно то значение, которое сохранил. Возможно, операции в этой программе совершались в таком порядке: поток t1 загружал начальную переменную, затем завершал все свои операции загрузки и сохранения, а потом поток t2 загружал начальную переменную и завершал все свои операции сохранения и загрузки. В результате потоку приходилось выполнять дополнительную работу, происходящую вне разделяемой памяти, например, for (int k = 0; k < 1000; ++k); . Если сделать так и запустить программу, то начнет проявляться разбежка между значениями, которые загружал каждый из потоков.

Операции обмена

Обмен (exchange), также называемый перестановкой (swap) – это операция, при которой в атомарной переменной заменяется некоторое значение и возвращается значение, которое было в ней ранее. Операции обмена – это и сохранение, и загрузка за один атомарный ход. Однако, до этой операции значение атомарной переменной не проверяется. Следовательно, обмен также может считаться безусловным. При обмене не предоставляется атомарного решения для загрузки с последующим сохранением.

Операцию обмена также иногда называют «системой с когерентным доступом к кэшу». Это значит, что, вслед за обменом, любая последующая операция отражает то значение, которое при обмене записывается в переменную. В качестве иллюстрации продемонстрирую, как в данном случае может использоваться единственный рабочий поток.

#include #include #include static std::atomic foobar(8); int main() < std::thread t1 = std::thread([]< int value = 4; for (int i = 0; i < 100000; ++i) < value = foobar.exchange(value); >>); printf("%d\n", foobar.load()); foobar.exchange(14); printf("%d\n", foobar.load()); printf("%d\n", foobar.load()); printf("%d\n", foobar.load()); printf("%d\n", foobar.load()); t1.join(); return 0; >

В вышеприведенном примере рабочий поток получает большую задачу: поменять значение 100 000 раз. В течение этого времени главная функция меняет значение, сохраненное в foobar , на 14 . Каждый последующий вызов вслед за обменом в главной функции теперь будет возвращать 14 .

Операции выборки данных

Операции выборки данных (fetch), например, «выбрать и сложить» или «выбрать и вычесть» применяют к атомарной переменной некоторую операцию и выбирают то значение, которое находилось в переменной до совершения операции. Операции выборки работают примерно так же, как операции обмена, в том смысле, что атомарный обмен заключается лишь в записи значения и «выборке» предыдущего значения. Есть несколько типов операций выборки, и в C++ поддерживаются следующие из них:

  • fetch_add
  • fetch_sub
  • fetch_and
  • fetch_or
  • fetch_xor

Одно ограничение атомарных операций выборки данных заключается в том, что они обеспечивают лишь эквивалент постфиксной операции, например, x++; . Операции выборки данных возвращают значение до операции и никогда после. Возвращение значения после операции потребовало бы дополнительной атомарной загрузки, из-за чего две операции стали бы неатомарными относительно значения переменной. В примере ниже реализован счетчик на основе операций fetch_add и fetch_sub .

#include #include struct Counter < Counter(): count(0) <>std::atomic count; unsigned operator++(int) < return count.fetch_add(1); >unsigned operator--(int) < return count.fetch_sub(1); >>; int main()

Если этот код скомпилировать и выполнить, он выведет на экран

0 1 2 1

Причина, по которой fetch_sub сначала возвращает 2, заключается в том, что fetch_add возвращает значение до того, как прирастить его. Следующий вызов fetch_sub возвращает 1, указывая, что предыдущий вызов вычел 1 после того, как было выбрано предыдущее значение.

Возможно, вы уже заметили, что у типичных арифметических операций, например, у умножения и деления, нет эквивалентов из рода fetch . Эта ситуация восходит к фундаментальной идее, лежащей в основе атомарных операций – всем им нужна аппаратная поддержка. Нет такого железа, которое поддерживало бы атомарное умножение или деление. В самом деле, существуют подходы с негарантированным результатом, реализующие такие операции программно. Например, можно поступить так: атомарно загрузить целое число, умножить его, а затем атомарно сохранить результат, чтобы получилось:

std::atomic a(3); int b = a.load() * 3; a.store(b);

Но при таком подходе возникает проблема. Возможно, что между второй и третьей строкой из предыдущего примера другой поток изменит значение a , поэтому цель «умножить на 3» не будет достигнута, ведь b – это произведение предыдущего значения a и 3. Если просто сохранить b в a , это может привести к несогласованности данных. В таком сценарии нет ничего опасного, отсутствует вероятность утечки в памяти или ошибки сегментации. Да, в многопоточной программе у всех потоков должно быть общее согласованное представление о данных с атомарными составляющими, чтобы задачи успешно решались. Поэтому в атомарной операции нужно предусмотреть концепцию «отказа».

Сравнение с обменом

Сравнение с обменом, также именуемое сравнением с перестановкой (CAS) – это самая мощная операция, доступная в C++. В большинстве случаев так обеспечивается атомарное сравнение актуального значения атомарной переменной и другого значения. Если результат сравнения будет true, то программа попытается сохранить желаемое значение. Несмотря на то, что сравнение с обменом является атомарной операцией, эта операция, конечно же, может закончиться неудачно – если в работу вмешается другой поток и изменит значение переменной между актом ее считывания и актом ее записи. Операция сравнения с обменом иначе называется «чтение-изменение-запись» (RMW).

Процессоры по-разному реализуют сравнение с обменом. В некоторых процессорах со строгим порядком операций, например, из семейства x86, сравнение с обменом выполняется в рамках единственной ассемблерной инструкции. Поэтому сравнение с обменом не удастся лишь в том случае, если другой поток действительно изменит значение атомарной переменной до того, как состоится операция сравнения с обменом. В процессорах с нестрогим порядком операций сравнение с обменом реализуется в двух ассемблерных инструкциях, как правило, по принципу LLCS (блокируемая загрузка и условное сохранение). LLCS может время от времени отказывать в силу использования двух инструкций – например, если у потока переключится контекст.

В C++ есть две функции, выполняющие сравнение с обменом, compare_exchange_weak и compare_exchange_strong . Слабая версия больше подходит для ситуаций, в которых операции вызываются циклически. Циклический вызов сравнения с обменом – распространенная ситуация, когда требуется реализовать неблокирующие структуры данных. Например, рассмотрим простейшую неблокирующую структуру данных такого рода – стек.

#include #include template class LFS < public: struct node < T data; node* next; node(const T& data) : data(data), next(nullptr) <> >; void push(const T& val) < node* new_node = new node(val); new_node->next = _head.load(); while(!_head.compare_exchange_weak(new_node->next, new_node)); > bool pop() < node* got = _head.load(); node* nextn = nullptr; do < if(got == nullptr) < return false; >nextn = got->next; > while(!_head.compare_exchange_weak(got, nextn)); delete got; return true; > private: std::atomic _head; >;

У вышеприведенного стека два метода — push и pop . Каждый из них удовлетворяет главному критерию неблокируемости: один поток постоянно прогрессирует и завершает свою задачу выталкивания из стека или записи в стек. Если будет много потоков вызовут compare_exchange_weak , то только у одного из них эта попытка будет успешной. Все остальные потоки потерпят с compare_exchange_weak неудачу – то есть, загрузят то значение, которое прямо сейчас сохранено в переменной. Такой цикл, фактически, обеспечивает, чтобы операция сравнения с обменом «повторялась с последним известным значением» атомарной переменной.

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

  • Блог компании Издательский дом «Питер»
  • Программирование
  • C++
  • Assembler
  • Параллельное программирование

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

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

Т.е. как я поняла, атомарные операции — это достаточно мелкие, выполняющиеся «за один шаг относительно других потоков». Но что значит этот «шаг»? Один шаг == одной машинной операции? Или чему-то другому? Как определить точно, какие операции относятся к атомарным, а какие к неатомарным? P.S.: Я нашла похожий вопрос, но там речь идёт о C#.

Отслеживать
задан 18 янв 2017 в 10:01
10.7k 6 6 золотых знаков 47 47 серебряных знаков 101 101 бронзовый знак

@Ksenia я не обладаю достаточной квалификацией для того, чтобы дать ее самостоятельно, но в википедии приводится следующая формулировка (хоть она и туманна): an operation (or set of operations) is atomic, linearizable, indivisible or uninterruptible if it appears to the rest of the system to occur instantaneously.. Попробую не забыть накатать вечером свой ответ.

18 янв 2017 в 10:38
Комментарии не предназначены для расширенной дискуссии; разговор перемещён в чат.
29 янв 2017 в 8:34

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Как можно определить атомарность?

Атомарность операции чаще всего принято обозначать через ее признак неделимости: операция может либо примениться полностью, либо не примениться вообще. Хорошим примером будет запись значений в массив:

public class Curiousity < public volatile int[] array; public void nonAtomic() < array = new int[1]; array[0] = 1; >public void probablyAtomic() < array = new int[] < 1 >; > > 

При использовании метода nonAtomic существует вероятность того, что какой-то поток обратится к array[0] в тот момент, когда array[0] не проинициализирован, и получит неожиданное значение. При использовании probablyAtomic (при том условии, что массив сначала заполняется, а уже потом присваивается — я сейчас не могу гарантировать, что в java это именно так, но представим, что это правило действует в рамках примера) такого быть не должно: array всегда содержит либо null, либо проинициализированный массив, но в array[0] не может содержаться что-то, кроме 1. Эта операция неделима, и она не может примениться наполовину, как это было с nonAtomic — только либо полностью, либо никак, и весь остальной код может спокойно ожидать, что в array будет либо null, либо значения, не прибегая к дополнительным проверкам.

Кроме того, под атомарностью операции зачастую подразумевают видимость ее результата всем участникам системы, к которой это относится (в данном случае — потокам); это логично, но, на мой взгляд, не является обязательным признаком атомарности.

Почему это важно?

Атомарность зачастую проистекает из бизнес-требований приложений: банковские транзакции должны применяться целиком, билеты на концерты заказываться сразу в том количестве, в котором были указаны, и т.д. Конкретно в том контексте, который разбирается (многопоточность в java), задачи более примитивны, но произрастают из тех же требований: например, если пишется веб-приложение, то разбирающий HTTP-запросы сервер должен иметь очередь входящих запросов с атомарным добавлением, иначе есть риск потери входящих запросов, а, следовательно, и деградация качества сервиса. Атомарные операции предоставляют гарантии (неделимости), и к ним нужно прибегать, когда эти гарантии необходимы.

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

Почему примитивные операции не являются атомарными сами по себе? Так же было бы проще для всех.

Современные среды исполнения очень сложны и имеют на борту некислый ворох оптимизаций, которые можно сделать с кодом, но, в большинстве случаев, эти оптимизации нарушают гарантии. Так как большинство кода этих гарантий на самом деле не требует, оказалось проще выделить операции с конкретными гарантиями в отдельный класс, нежели наоборот. Чаще всего в пример приводят изменение порядка выражений — процессор и JVM имеют право выполнять выражения не в том порядке, в котором они были описаны в коде, до тех пор, пока программист не будет форсировать определенный порядок выполнения с помощью операций с конкретными гарантиями. Также можно привести пример (не уверен, правда, что формально корректный) с чтением значения из памяти:

thread #1: set x = 2 processor #1: save_cache(x, 2) processor #1: save_memory(x, 2) thread #2: set x = 1 processor #2: save_cache(x, 1) processor #2: save_memory(x, 1) thread #1: read x processor #1: read_cache(x) = 2 // в то время как х уже был обновлен значением 1 в thread #2 

Здесь не используется т.н. single source of truth для того, чтобы управлять значением Х, поэтому возможны такие аномалии. Насколько понимаю, чтение и запись напрямую в память (или в память и в общий кэш процессоров) — это как раз то, что форсирует модификатор volatile (здесь могу быть неправ).

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

Это относится только к операциям связанным с установкой переменных и прочей процессорной сфере деятельности?

Нет. Любая операция может быть атомарной или неатомарной, например, классические реляционные базы данных гарантируют, что транзакция — которая может состоять из изменений данных на мегабайты — либо применится полностью, либо не будет применена. Процессорные инструкции здесь не имеют никакого отношения; операция может быть атомарной до тех пор, пока она является атомарной сама по себе или ее результат проявляется в виде другой атомарной операции (например, результат транзакции базы данных проявляется в записи в файл).

Кроме того, насколько понимаю, утверждение «инструкция не успела за один цикл — операция неатомарна» тоже неверно, потому что есть некоторые специализированные инструкции, и никто не мешает атомарно устанавливать какое-либо значение в памяти на входе в защищенный блок и снимать его по выходу.

Любая ли операция может быть атомарной?

Нет. Мне очень сильно не хватает квалификации для корректных формулировок, но, насколько понимаю, любая операция, подразумевающая два и более внешних эффекта (сайд-эффекта), не может быть атомарной по определению. Под сайд-эффектом в первую очередь подразумевается взаимодействие с какой-то внешней системой (будь то файловая система или внешнее API), но даже два выражения установки переменных внутри synchronized-блока нельзя признать атомарной операцией, пока одно из них может выкинуть исключение — а это, с учетом OutOfMemoryError и прочих возможных исходов, может быть вообще невозможно.

У меня операция с двумя и более сайд-эффектами. Могу ли я все-таки что-нибудь с этим сделать?

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

client: journal.push , reserveTicket , sendEmail > client: journal: process withdrawMoney journal: markCompleted withdrawMoney journal: process reserveTicket journal: journal: journal: process reserveTicket # сайд-эффект вызывается еще раз, но только в случае некорректной работы journal: markCompleted reserveTicket journal: process sendEmail journal: markCompleted sendEmail 

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

Как все-таки определить атомарность операций в java?

Первичный источник правды в этом случае — это Java Memory Model, которая определяет, какие допущения и гарантии применяются к коду в JVM. Java Memory Model, впрочем, довольно сложна для понимания и покрывает значительно большую сферу операций, нежели сфера атомарных операций, поэтому в контексте этого вопроса достаточно знать, что модификатор volatile обеспечивает атомарное чтение и запись, а классы Atomic* позволяют производить compare-and-swap операции, чтобы атомарно менять значения, не боясь, что между чтением и записью придет еще одна чья-то запись, а в комментариях ниже на момент прочтения наверняка добавили еще что-то, что я забыл.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *