Подтвердить что ты не робот

Является ли поток операторов ++ безопасным?

ПРИМЕЧАНИЕ. Я действительно не очень хорошо разбираюсь в многопоточном программировании, но в моем текущем проекте мне это делается, поэтому я пытаюсь понять, что является потокобезопасным, а что нет.

Я читал один из Eric Lippert удивительных ответов на то, что ++ я делаю. Он говорит, что это действительно так:

  • x вычисляется для создания переменной
  • значение переменной копируется во временное расположение
  • временное значение увеличивается, чтобы создать новое значение (не перезаписывая временное!)
  • новое значение сохраняется в переменной
  • результатом операции является новое значение

Это заставило меня подумать, что, если два потока, где вызывается ++ i? Если первый поток находится на шаге 3, когда второй поток находится на шаге 2. (Что означает, что если второй поток копирует значение в временное местоположение до того, как первый поток сохранит новое значение в переменной?)

Если это произойдет, тогда казалось бы, что оба потока будут только увеличивать i один раз вместо двух. (Если все это не в lock.)

4b9b3361

Ответ 1

Как указывали другие ответы, нет, ++ не является "потоковым".

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

(Существует множество других проблем с потоками, которые не имеют ничего общего с атомарностью, но которые могут по-прежнему подпадать под определённые определения безопасности потоков. Например, если два потока, каждый из которых изменяет переменную, и два потока, каждый из которых считывает переменную, два читателя гарантируют согласие на порядок, в котором два других потока сделали мутации? Если ваша логика зависит от этого, тогда у вас есть очень трудная проблема безопасности потоков, с которой можно справиться, даже если каждое чтение и запись является атомарным.)

В С# практически ничего не гарантируется атомарным. Вкратце:

  • чтение 32-битного целого числа или float
  • чтение ссылки
  • запись 32-битного целого числа или float
  • запись ссылки

гарантированно являются атомными (см. спецификацию для точных деталей.)

В частности, чтение и запись 64-битного целого числа или float не гарантируется атомарным. Если вы скажете:

C.x = 0xDEADBEEF00000000;

в одном потоке и

C.x = 0x000000000BADF00D;

в другом потоке, то это возможно в третьем потоке:

Console.WriteLine(C.x);

имеют, что выписывают 0xDEADBEEF0BADF00D, хотя логически переменная никогда не удерживала это значение. Язык С# оставляет за собой право сделать запись длинным эквивалентом записи в два ints один за другим, и на практике некоторые чипы реализуют его таким образом. Переключатель потока после первой записи может заставить читателя прочитать что-то неожиданное.

Длинные и короткие: не разделяйте ничего между двумя потоками, не блокируя что-то. Замки только медленны, когда они довольны; если у вас есть проблема с производительностью из-за конкурирующих замков, то исправить любой архитектурный недостаток, ведущий к конкурирующим замкам. Если блокировки не утверждаются и все еще слишком медленны, только тогда вы должны подумать о том, чтобы перейти к опасным методам с низким уровнем блокировки.

Общей методикой с низким уровнем блокировки для использования здесь является, конечно, вызов Threading.Interlocked.Increment, который делает приращение целого числа гарантированным атомом. (Обратите внимание, однако, что он по-прежнему не дает гарантий относительно таких вещей, как то, что происходит, если каждый из двух потоков выполняет взаимные блокировки двух разных переменных в разное время, а другие потоки пытаются определить, какое инкремент произошло "первым". С# не гарантирует, что все непрерывные последовательности событий просматриваются всеми потоками.)

Ответ 2

Нет, это не так.

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

Это сложно сделать правильно, но, к счастью, BCL имеет готовое решение для этих конкретных случаев:

Вы должны использовать Interlocked.Increment, если хотите операцию атомного приращения. Существует также Decrement и несколько других полезных атомных операций, определенных на Interlocked.

Увеличивает заданную переменную и сохраняет результат как атомную операцию.

Ответ 3

Нет, i++ выглядит компактным, но на самом деле это просто сокращение для i = i + 1, и в этой форме легче видеть, что оно включает чтение и запись "i". Без блокировки это просто небезопасно.

2 других аргумента:

  • `++ 'не определен как потокобезопасный
  • существование Interlocked.Increment (ref int x)

Безопасность потоков редка и дорогостоящая, она будет четко рекламироваться, когда она доступна.