2016-06-01 5 views
11

Написав ответ на вопрос another question, появилось несколько интересных вещей, и теперь я не могу понять, как Interlocked.Increment(ref long value) работает на 32-битных системах. Позволь мне объяснить.Атомное приращение 64-битной переменной в 32-битной среде

InterlockedIncrement64 Native теперь не доступен при компиляции для 32-битной среды, хорошо, это имеет смысл, потому что в .NET вы не можете выровнять память по мере необходимости, и она может быть вызвана из удался затем сбросили его.

В .NET мы можем назвать Interlocked.Increment() со ссылкой на переменную 64-битной, мы все еще не имеют каких-либо ограничений относительно его выравнивания (например, в структуре, также, где мы можем использовать FieldOffset и StructLayout), но документация Безразлично Ограничение (AFAIK). Это волшебство, это работает!

Ханс Passant отметил, что Interlocked.Increment() является специальный метод признан JIT компилятором, и он будет излучать вызов COMInterlocked::ExchangeAdd64(), который будет затем вызвать FastInterlockExchangeAddLong, который представляет собой макрос InterlockedExchangeAdd64, который разделяет те же ограничения из InterlockedIncrement64.

Теперь я озадачен.

Забудьте в течение одной секунды управляемой среды и вернитесь к родной. Почему InterlockedIncrement64 не может работать, но InterlockedExchangeAdd64 делает? InterlockedIncrement64 макрос, если встроенные функции не доступны и InterlockedExchangeAdd64 работы, то он может быть реализован как призыв к InterlockedExchangeAdd64 ...

Давайте вернемся к управляемым: как атомный 64 бит прирост реализован на 32-битных системах? Я полагаю, что предложение «Эта функция является атомарной в отношении вызовов к другим взаимосвязанным функциям.« важен, но все же я не видел никакого кода (спасибо Хансу указать на более глубокую реализацию), чтобы сделать это. Давайте выберем InterlockedExchangedAdd64 реализацию из WinBase.h, когда встроенные функции не доступны:

FORCEINLINE 
LONGLONG 
InterlockedExchangeAdd64(
    _Inout_ LONGLONG volatile *Addend, 
    _In_ LONGLONG Value 
    ) 
{ 
    LONGLONG Old; 

    do { 
     Old = *Addend; 
    } while (InterlockedCompareExchange64(Addend, 
              Old + Value, 
              Old) != Old); 

    return Old; 
} 

Как это может быть атомарным для чтения/записи?

+0

Кто сказал, что «InterlockedIncrement64» не может работать, но «InterlockedExchangeAdd64' делает»? Ваш первоначальный ответ был прав, говоря, что управляемый код не может напрямую вызывать собственные API Win32 и ожидать, что все будет работать. Ни один из них не собирается работать. Вы должны использовать управляемый помощник. Теперь реализация управляемого помощника - это собственный код, поэтому он вызывает функцию native. Поскольку макросы и внутренности решаются во время компиляции, то считается, что бит CLR. –

+0

Да, но 32-разрядный JIT вызовет InterlockedExchangeAdd64, который имеет те же ограничения (в native), что и InterlockedIncrement64. Я не понял, как это можно сделать (из-за выравнивания памяти при вызове управляемого кода). Реализация на 32-битном языке использует InterlockedCompareExchange64, который ... hmmm .... может быть не атомарным (для записи результата обратно ...) –

+1

* «Как он может быть атомарным для чтения/записи?» * Документация для подсказок 'InterlockedExchangeAdd64' по этой причине, говоря * «Эта функция генерирует полный барьер памяти (или забор), чтобы гарантировать, что операции с памятью выполняются по порядку». * Обратите внимание, что реализация, которую вы показываете выше, вызывает «InterlockedCompareExchange64». В 32-битных сборках это генерирует инструкцию 'CMPXCHG8B' с префиксом' LOCK'. Это гарантирует, что инструкция выполняется атомарно. Вы никогда не получаете запертое чтение без заблокированной записи, поэтому запись адресата является атомарной. –

ответ

9

Вы должны следить за тропой, InterlockedExchangeAdd64() перенесет вас в файл заголовка WinNt.h SDK. Где вы увидите много версий, в зависимости от целевой архитектуры.

Это, как правило коллапсирует:

#define InterlockedExchangeAdd64 _InterlockedExchangeAdd64 

который проходит бакс к компилятору внутреннему, объявленному в ОМ/включать/intrin.h и осуществляются фоновым составитель.

Другими словами, различные сборки CLR будут иметь разные реализации. На протяжении многих лет многие из них, x86, x64, Itanium, ARM, ARM8, PowerPC у меня на голове, я, конечно же, пропускаю некоторые, которые использовались для загрузки WindowsCE, прежде чем Apple сделало это неуместным. Для x86 это в конечном счете заботится LOCK CMPXCHNG8B, специальная инструкция процессора, которая может обрабатывать смещенные 64-битные переменные. У меня нет оборудования, чтобы посмотреть, как он выглядит на других 32-разрядных процессорах.

Имейте в виду, что целевая архитектура управляемого кода не прибита во время компиляции. Это дрожание, которое адаптирует MSIL к цели во время выполнения.Это не так актуально для проектов C++/CLI, так как вам обычно нужно выбрать цель, если вы компилируете с/clr вместо/clr: pure и только x86 и x64 могут работать. Но сантехника на месте так или иначе, поэтому макрос просто не очень полезен.

+0

Извините, я не понимаю. Предполагая, что реализация InterlockedExchangeAdd64 хорошо работает на 32-битной (без instrinsic!), То макрос InterlockedIncrement64 может быть реализован таким же образом (в 32-битной управляемой среде). Расширение этого, а также встроенные функции на 64-битной версии могут быть надежно реализованы на 32 бит. Как?! Ну, я думаю, что формулировка _ «Эта функция является атомарной ** по отношению к вызовам других взаимосвязанных функций **.» _ Является ключом, но по пути я не вижу никакого _special_ кода для этого –

+0

Не так уверен, где зависание лежит, макросы работают только во время компиляции. Что хорошо, когда код находится внутри CLR, у вас будет соответствующая версия его во время выполнения, соответствующая целевой архитектуре. Таким образом, вызов в вспомогательную функцию всегда выполняется. Это не хорошо во время компиляции в вашей собственной сборке .NET, поскольку она может работать на разных платформах. Все еще получая его преобразованный в встроенный ассемблерный код во время выполнения (LOCK XADD), когда целевая платформа позволяет это, конечно же, сладкое пятно. Это процессор, который обеспечивает гарантию в любом случае. –

+0

Да, конечно, я понимаю, что макросы должны быть разрешены во время компиляции. Предположим, что у вас смешанный проект C++/CLI, ориентированный на Win32. Вы можете вызвать Interlocked :: Increment (длинный длинный). Во время выполнения JIT заменит этот вызов своей собственной вспомогательной функцией. Теперь: 1) если эта вспомогательная функция существует, то также InterlockedIncrement64 может быть реализован таким же образом (вместо сброса). 2) Как он может быть атомарным, если: a) вы на 32-битной системе и b) не нужно выравнивать память? –