Инкрементът цяло число атомно ли е в x86?

На многоядрена x86 машина, да кажем, че нишка, изпълнявана на core1, увеличава целочислена променлива a в същото време нишката на core 2 също я увеличава. Като се има предвид, че първоначалната стойност на a беше 0, винаги ли ще бъде 2 накрая? Или може да има друга стойност? Да приемем, че a е декларирано като volatile и ние не използваме атомарни променливи (като atomic‹> на C++ и вградени атомарни операции в gcc).

Ако стойността на a наистина винаги ще бъде 2 в такъв случай, означава ли това, че long int в x86-64 също ще има същото свойство, тоест a винаги ще бъде 2 накрая?


person pythonic    schedule 08.05.2012    source източник
comment
освен ако не използвате специален атомен тип, нарастването обикновено е три отделни операции. Зареждане, увеличаване, след това съхраняване.   -  person Hunter McMillen    schedule 08.05.2012
comment
volatile не ви дава атомен достъп.   -  person Cat Plus Plus    schedule 08.05.2012
comment
@CatPlusPlus твоето име атомарна операция ли е? :P   -  person MByD    schedule 08.05.2012
comment
достъпът до същия тип би довел до състояние на състезание и ако не се внимава правилно, стойността би била неочаквана. тук в този случай кажете, че нишка 1 и нишка 2 зареждат стойност по едно и също време и я правят 2 всяка, така че без значение дали нишка 1 или нишка 2, който я зареди пръв, ще доведе до крайната стойност като 2   -  person Invictus    schedule 08.05.2012
comment
@CatPlusPlus: Има разширение на компилатора на MS, което кара летливите променливи да имат атомни записи (които са атомни на нишки). Не мисля, че това се простира до g++. Също така не съм 100% запознат с функционалността, тъй като не използвам. Така че вижте msdn.microsoft.com/en-us/library/12a04hfd.aspx   -  person Martin York    schedule 08.05.2012
comment
Четенето и писането са атомарни, стига да не пресичате границата на линията на кеша. Извършването на двете с друга операция между не е автоматично атомно.   -  person Mark Ransom    schedule 08.05.2012
comment
@MarkRansom Може би. В зависимост от това как дефинирате атомния. Най-новата употреба (включително в стандарта C++) му придава по-стриктно значение, така че без специални допълнителни инструкции никакви записи или четения не са атомарни.   -  person James Kanze    schedule 08.05.2012


Отговори (4)


Машинната инструкция за увеличаване на паметта на X86 е атомарна само ако я използвате с префикс LOCK.

x++ в C и C++ няма атомарно поведение. Ако направите отключени увеличения, поради състезания, в които процесорът чете и записва X, ако два отделни процесора се опитат да направят увеличение, можете да се окажете само с едно увеличение или и двете (вторият процесор може да е прочел първоначалната стойност, увеличена и го записва обратно, след като първият записва своите резултати обратно).

Вярвам, че C++11 предлага атомни увеличения и повечето компилатори на доставчици имат идиоматичен начин да предизвикат атомно увеличение на определени вградени цели числа (обикновено int и long); вижте справочното ръководство на вашия компилатор.

Ако искате да увеличите "голяма стойност" (да речем, цяло число с много точност), трябва да го направите с помощта на някакъв стандартен заключващ механизъм, като например семафор.

Имайте предвид, че трябва да се тревожите и за атомарните четения. На x86 четенето на 32- или 64-битова стойност се оказва атомарно, ако е подравнена с 64-битова дума. Това няма да е вярно за „голяма стойност“; отново ще ви трябва стандартна ключалка.

person Ira Baxter    schedule 08.05.2012
comment
Префиксът LOCK осигурява ли и необходимите огради? (Мисля, че да, но не съм сигурен.) - person James Kanze; 08.05.2012

Ето едно доказателство, че не е атомарно в конкретно изпълнение (gcc). Както можете да видите (?), gcc генерира код, който

  1. зарежда стойността от паметта в регистър
  2. увеличава съдържанието на регистъра
  3. записва регистъра обратно в паметта.

Това е много далеч от това да е атомно.

$ cat t.c
volatile int a;

void func(void)
{
    a++;
}
[19:51:52 0 ~] $ gcc -O2 -c t.c
[19:51:55 0 ~] $ objdump -d t.o

t.o:     file format elf32-i386


Disassembly of section .text:

00000000 <func>:
   0:   a1 00 00 00 00          mov    0x0,%eax
   5:   83 c0 01                add    $0x1,%eax
   8:   a3 00 00 00 00          mov    %eax,0x0
   d:   c3                      ret

Не се заблуждавайте от 0x0 в инструкцията mov, там има място за 4 байта и линкерът ще попълни получения адрес на паметта за a там, когато този обектен файл е свързан.

person nos    schedule 08.05.2012
comment
Това е смешно, всъщност получавате отделно зареждане/добавяне/съхраняване само когато a е volatile, в противен случай gcc използва четене-промяна-запис (освен ако не се настройва за i586 (pentium)). Разбира се, ако околният код използва стойността на num++, това много вероятно ще бъде направено с отделни инструкции за оставяне на резултата в регистър. Споменах това в отговора си на неvolatile версия на въпроса . - person Peter Cordes; 12.10.2016
comment
@PaterCordes Без volatile gcc може да генерира една единствена инструкция addl , въпреки че и тя не е атомарна, освен ако не използвате и префикса lock. - person nos; 12.10.2016
comment
:P Втората връзка в предишния ми коментар е към моя отговор, който обяснява точно (от гледна точка на протокола MESI и т.н.) защо addl $1, num не е атомен (освен при еднопроцесорна система, ако не включим DMA наблюдатели) и защо е с lock. - person Peter Cordes; 12.10.2016

Тъй като никой не е отговорил на действителния ви въпрос и вместо това ви показва как да го направите по начин, който винаги работи:

Нишка 1 зарежда стойност 0

Нишка 2 зарежда стойност 0

Нишка 1 увеличава и съхранява 1

Нишка 2 увеличава своето локално регистърно копие на стойност и съхранява 1.

Както можете да видите, крайният резултат е стойност, равна на 1, а не на 2. Не винаги ще бъде 2 в края.

person Michael Dorgan    schedule 08.05.2012

Не е гарантирано. Можете да използвате инструкцията lock xadd, за да постигнете същия ефект, или да използвате C++ std::atomic, или да използвате #pragma omp atomic, или произволен брой други решения за едновременност, които са написани, за да ви спестят неприятностите да преоткривате колелото.

person Jon Purdy    schedule 08.05.2012