Атомная инструкция

Что вы имеете в виду под атомными инструкциями?

Как следующее становится атомарным?

TestAndSet

int TestAndSet(int *x){
   register int temp = *x;
   *x = 1;
   return temp;
}

С точки зрения программного обеспечения, если кто-то не хочет использовать неблокирующие примитивы синхронизации, как можно обеспечить атомарность инструкции? возможно ли это только на Аппаратном уровне или может использоваться некоторая директивная оптимизация на уровне сборки?


person Sashi    schedule 19.11.2009    source источник


Ответы (5)


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

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

Более сложные вещи, такие как чтение и запись вместе атомарно, могут быть достигнуты с помощью явных атомных машинных инструкций, например. ЗАБЛОКИРОВАТЬ CMPXCHG на x86.

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

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

person Will    schedule 19.11.2009

Атомик происходит от греческого ἄτομος (атомос), что означает «неделимый». (Предостережение: я не говорю по-гречески, так что, возможно, это действительно что-то другое, но большинство англоговорящих людей, ссылаясь на этимологию, интерпретируют это так. :-)

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

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

if (foo == 0)
{
   foo = some_function();
}

Но что, если это будет выполняться параллельно? Может случиться так, что программа получит foo, увидит его как ноль, в то время как поток 2 делает то же самое и устанавливает какое-то значение. Вернувшись в исходный поток, код по-прежнему считает, что foo равен нулю, и переменная назначается дважды.

Для подобных случаев ЦП предоставляет некоторые инструкции, которые могут выполнять сравнение и условное присвоение как атомарную сущность. Следовательно, test-and-set, compare-and-swap и load-connected / store-conditional. Вы можете использовать их для реализации блокировок (ваша ОС и ваша библиотека C сделали это). Или вы можете написать разовые алгоритмы, которые полагаются на примитивы, чтобы что-то делать. (Здесь есть кое-что интересное, но большинство простых смертных избегают этого, опасаясь ошибиться.)

person asveikau    schedule 19.11.2009

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

Определение:

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

Пример 1. Атомарные операции

  • Рассмотрим следующие целые числа, используемые разными потоками:

     int X = 2;
     int Y = 1;
     int Z = 0;
    
     Z = X;  //Thread 1
    
     X = Y;  //Thread 2
    
  • В приведенном выше примере два потока используют X, Y и Z

  • Каждое чтение и запись атомарны
  • The threads will race :
    • If thread 1 wins, then Z = 2
    • Если поток 2 выигрывает, то Z = 1
    • Z определенно будет одним из этих двух значений

Пример 2: Неатомарные операции: ++ / - Операции

  • Рассмотрим выражения увеличения / уменьшения:

    i++;  //increment
    i--;  //decrement
    
  • Операции переводятся в:

    1. Read i
    2. Увеличение / уменьшение считываемого значения
    3. Запишите новое значение обратно в i
  • Каждая операция состоит из 3-х атомарных операций и сами по себе не являются атомарными.
  • Две попытки увеличить i в отдельных потоках могут чередоваться, так что одно из приращений теряется

Пример 3. Неатомарные операции: значения больше 4 байтов

  • Рассмотрим следующую неизменяемую структуру:
  struct MyLong
   {
       public readonly int low;
       public readonly int high;

       public MyLong(int low, int high)
       {
           this.low = low;
           this.high = high;
       }
   }
  • # P10 #
    MyLong X = new MyLong(0xAAAA, 0xAAAA);   
    MyLong Y = new MyLong(0xBBBB, 0xBBBB);     
    MyLong Z = new MyLong(0xCCCC, 0xCCCC);
    
  • # P11 #
    X = Y; //Thread 1                                  
    Y = X; //Thread 2
    
  • В .NET при копировании типа значения среда CLR не вызывает конструктор - она ​​перемещает байты по одной атомарной операции за раз.

  • Из-за этого операции в двух потоках теперь являются четырьмя атомарными операциями.
  • Если потокобезопасность не применяется, данные могут быть повреждены.
  • # P13 #
    X.low = Y.low;      //Thread 1 - X = 0xAAAABBBB            
    Y.low = Z.low;      //Thread 2 - Y = 0xCCCCBBBB              
    Y.high = Z.high;    //Thread 2 - Y = 0xCCCCCCCC             
    X.high = Y.high;    //Thread 1 - X = 0xCCCCBBBB   <-- corrupt value for X
    
  • Чтение и запись значений, превышающих 32-битные, в нескольких потоках в 32-битной операционной системе без добавления какой-либо блокировки, чтобы сделать операцию атомарной, вероятно, приведет к повреждению данных, как указано выше.

Операции процессора

  • На всех современных процессорах можно предположить, что чтение и запись естественно выровненных собственных типов являются атомарными до тех пор, пока:

    • 1 : The memory bus is at least as wide as the type being read or written
    • 2: ЦП считывает и записывает эти типы в одной транзакции шины, что делает невозможным для других потоков видеть их в полузавершенном состоянии.
  • На x86 и X64 нет гарантии, что чтение и запись размером более восьми байт являются атомарными.

  • Производители процессоров определяют атомарные операции для каждого процессора в Руководство разработчика ПО
  • В одноядерных / одноядерных системах можно использовать стандартные методы блокировки для предотвращения прерывания инструкций ЦП, но это может быть неэффективным.
  • Если возможно, отключение прерываний - еще одно более эффективное решение.
  • В многопроцессорных / многоядерных системах все еще можно использовать блокировки, но простое использование одной инструкции или отключение прерываний не гарантирует атомарный доступ.
  • Атомарность может быть достигнута путем обеспечения того, что используемые инструкции подтверждают сигнал LOCK на шине, чтобы предотвратить одновременный доступ к памяти другими процессорами в системе.

Языковые различия

C #

  • C # гарантирует, что операции с любым встроенным типом значения, занимающим до 4 байтов, являются атомарными.
  • Не гарантируется, что операции с типами значений, занимающие более четырех байтов (double, long и т. Д.), Будут атомарными.
  • The CLI guarantees that reads and writes of variables of value type that are the size (or smaller) of the processor's natural pointer size are atomic
    • Ex - running C# on a 64-bit OS in a 64-bit version of the CLR performs reads and writes of 64-bit doubles and long integers atomically
  • Creating atomic operations :
    • .NET provodes the Interlocked Class as part of the System.Threading namespace
    • Класс Interlocked обеспечивает атомарные операции, такие как приращение, сравнение, обмен и т. Д.
using System.Threading;             

int unsafeCount;                          
int safeCount;                           

unsafeCount++;                              
Interlocked.Increment(ref safeCount);

C ++

  • Стандарт C ++ не гарантирует атомарного поведения
  • Все операции C / C ++ считаются неатомарными, если иное не указано компилятором или поставщиком оборудования, включая присвоение 32-битных целых чисел.
  • Creating atomic operations :
    • The C++ 11 concurrency library includes the - Atomic Operations Library ()
    • Библиотека Atomic предоставляет атомарные типы в качестве шаблонного класса для использования с любым типом, который вы хотите.
    • Операции с атомарными типами являются атомарными и, следовательно, потокобезопасными.

struct AtomicCounter
{

   std::atomic< int> value;   

   void increment(){                                    
       ++value;                                
   }           

   void decrement(){                                         
       --value;                                                 
   }

   int get(){                                             
       return value.load();                                    
   }      

}

Java

  • Java гарантирует, что операции с любым встроенным типом значения, занимающим до 4 байтов, являются атомарными.
  • Присвоения волатильных длинных и двойных позиций также гарантированно атомарны.
  • Java предоставляет небольшой набор классов, которые поддерживают поточно-ориентированное программирование без блокировки для отдельных переменных через java.util.concurrent.atomic.
  • This provides atomic lock-free operations based on low-level atomic hardware primitives such as compare-and-swap (CAS) - also called compare and set :
    • CAS form - boolean compareAndSet(expectedValue, updateValue );
      • This method atomically sets a variable to the updateValue if it currently holds the expectedValue - reporting true on success
import java.util.concurrent.atomic.AtomicInteger;

public class Counter
{
     private AtomicInteger value= new AtomicInteger();

     public int increment(){
         return value.incrementAndGet();  
     }

     public int getValue(){
         return value.get();
     }
}

Источники
http://www.evernote.com/shard/s10/sh/c2735e95-85ae-4d8c-a615-52aadc305335/99de177ac05dc8635fb42e4e6121f1d2

person M0eB    schedule 02.03.2014

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

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

Если вы это сделаете (я буду использовать C, так как это то, что в вашем примере):

 ...
 f = fopen ("SYNCFILE","r");
 if (f == NULL) {
   f = fopen ("SYNCFILE","w");
 }
 ...

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

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

person Remo.D    schedule 19.11.2009

Атомарность может гарантировать только ОС. Для этого ОС использует базовые функции процессора.

Поэтому создание собственной функции testandset невозможно. (Хотя я не уверен, можно ли использовать встроенный фрагмент asm и использовать мнемонику testandset напрямую (может быть, этот оператор может быть выполнен только с привилегиями ОС))

РЕДАКТИРОВАТЬ: Согласно комментариям под этим сообщением, возможно создание вашей собственной функции bittestandset с использованием директивы ASM напрямую (на Intel x86). Однако неясно, работают ли эти уловки и на других процессорах.

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

person Toad    schedule 19.11.2009
comment
Атомарность может гарантировать только ОС. Для этого ОС использует базовые функции процессора. ... (Может быть, этот оператор может быть выполнен только с привилегиями ОС)) Нет. Атомарность гарантируется инструкциями ЦП (одиночные атомарные инструкции и ограждения памяти), а не операционной системой. Возможно, вам придется использовать язык ассемблера или расширение компилятора C, обертывающее вызов сборки, но фактическая атомарная инструкция не зависит от ОС. - person kquinn; 19.11.2009
comment
Не правда! Вопиющая ложь! Вы можете выполнять атомарные инструкции в обычном пользовательском коде. asm (lock compxchgl ...) и т. д. - person asveikau; 19.11.2009
comment
asveikau: Я сказал, что не уверен, нужно ли делать эти asm-операторы с более высокими привилегиями. Неуверенность или откровенная ложь - две разные вещи; ^) - person Toad; 19.11.2009
comment
Что ж, самое обидное было то, что это было гарантировано только ОС. Во многих случаях ОС не используется. Это довольно часто встречается даже в стороннем коде. Я также работал с несколькими наборами инструкций ЦП (x86, power, sparc, ia64) и не могу представить себе тот, который имеет атомарные инструкции как привилегированные ... - person asveikau; 19.11.2009
comment
asveikau: Я также не могу представить, чтобы кто-то выполнял атомарный бит кода и НЕ использовал вызовы типов блокировки или семафоров, доступные в ОС. Так что теоретически может быть так, что, очевидно, можно использовать фрагмент сборки в пользовательском режиме, в 99,99% случаев он никогда не будет использоваться. - person Toad; 27.11.2009