Запутался в использовании Interlocked.Increment и Lock
Я понимаю функциональность Interlocked.Increment
а также lock()
, Но я не понимаю, когда использовать один или другой. Насколько я могу судить Interlocked.Increment
увеличивает общее значение int / long, тогда как как lock()
предназначен для блокировки области кода.
Например, если я хочу обновить строковое значение, это возможно с lock()
:
lock(_object)
{
sharedString = "Hi";
}
Однако это невозможно с Interlocked
учебный класс.
- Почему это не может быть сделано через
Interlocked
? - В чем разница между этими механизмами синхронизации?
3 ответа
Interlocked.Increment
и связанные методы полагаются на аппаратные инструкции для выполнения синхронизированной модификации одного 32-битного или 64-битного значения памяти, гарантируя, что несколько потоков, обращающихся к одному и тому же значению, не будут читать / записывать устаревшие данные. это необходимо, поскольку на аппаратном уровне процессор имеет локальную / шинную копию значений памяти (для производительности, часто называемой шинной памятью или кэшем ЦП).
lock(){}
выполняет синхронизацию для части кода, а не для одного целого значения. и вместо того, чтобы полагаться на аппаратные инструкции для синхронизации доступа к переменной, результирующий код вместо этого полагается на примитивы синхронизации операционной системы (программные, а не аппаратные) для защиты памяти и выполнения кода.
Кроме того, использование lock() создает барьер памяти, гарантируя, что при доступе к одним и тем же переменным из нескольких процессоров получаются синхронизированные (не устаревшие) данные. Это не верно в других языках / платформах, где барьеры памяти и ограждение должны быть явно выполнены.
Более эффективно использовать Interlocked
методы для целочисленных значений, потому что аппаратное обеспечение имеет встроенную поддержку для выполнения необходимой синхронизации. но эта аппаратная поддержка существует только для собственных интегралов, таких как __int32 и __int64, поскольку в оборудовании нет понятия сложных типов более высокого уровня, такой высокоуровневый метод не раскрывается из Interlocked
тип. Таким образом, вы не можете использовать Interlocked
синхронизировать назначение System.String
или любой System.Object
производные типы.
(Даже если присвоение указателя на строковое значение может быть выполнено с помощью той же аппаратной инструкции, если вы использовали язык более низкого уровня, факт заключается в том, что в.NET строковый объект не представлен как указатель и, следовательно, он просто не возможно на любом "чистом" языке.NET. Я избегаю того факта, что вы можете использовать небезопасные методы для разрешения указателя и выполнения блокированного присваивания строковых значений, если вы действительно этого хотите, но я не думаю, что это действительно о чем вы спрашиваете, и далее это не поддерживается Interlocked, потому что под капотом должно произойти пиннинг GC, который, вероятно, станет более дорогим и агрессивным, чем использование lock()
.)
Таким образом, для синхронизированной модификации / назначения "ссылочных типов" вам нужно будет использовать примитив синхронизации (т. Е. Lock(){}, Monitor и т. Д.). Если все, что вам нужно для синхронизации - это одно целое значение (Int32, Int64), было бы более эффективно использовать методы Interlocked. Может все же иметь смысл использовать оператор lock(), если существует несколько целочисленных значений для синхронизации, например, увеличивая одно целое число, уменьшая второе целое, где оба должны быть синхронизированы как одна логическая операция.
Interlocked.Increment
можно и нужно использовать для увеличения общего int
переменная. Функционально используя Interlocked.Increment
такой же как:
lock(_object)
{
counter++;
}
но Interlocked.Increment
намного дешевле с точки зрения производительности.
Если вы хотите обменять ссылочное значение и вернуть исходное значение в элементарной операции, вы можете использовать Interlocked.Exchange
, Interlocked.Increment
делает именно то, что говорит, делает: увеличивает число.
Но просто присвоение ссылочного значения переменной или любому 32-битному типу значения в любом случае является атомарным в.NET. Единственный другой случай, о котором я могу подумать, в котором последнее не выполняется, - это если вы создаете упакованную структуру и устанавливаете атрибуты, которые заставят компилятор не выравнивать элементы на 4-байтовых границах (но это не то, что вы делаете действительно часто).