Ответ 1
Как указывали другие ответы, нет, ++ не является "потоковым".
Что-то, что я думаю, поможет, когда вы узнаете о многопоточности и его опасностях, - это начать очень точно о том, что вы подразумеваете под "threadafe", потому что разные люди имеют в виду разные вещи. По существу, аспект безопасности потоков, который вас беспокоит, заключается в том, является ли операция атомарной или нет. "Атомная" операция - это операция, которая гарантированно не будет заполнена на полпути, если она прерывается другим потоком.
(Существует множество других проблем с потоками, которые не имеют ничего общего с атомарностью, но которые могут по-прежнему подпадать под определённые определения безопасности потоков. Например, если два потока, каждый из которых изменяет переменную, и два потока, каждый из которых считывает переменную, два читателя гарантируют согласие на порядок, в котором два других потока сделали мутации? Если ваша логика зависит от этого, тогда у вас есть очень трудная проблема безопасности потоков, с которой можно справиться, даже если каждое чтение и запись является атомарным.)
В С# практически ничего не гарантируется атомарным. Вкратце:
- чтение 32-битного целого числа или float
- чтение ссылки
- запись 32-битного целого числа или float
- запись ссылки
гарантированно являются атомными (см. спецификацию для точных деталей.)
В частности, чтение и запись 64-битного целого числа или float не гарантируется атомарным. Если вы скажете:
C.x = 0xDEADBEEF00000000;
в одном потоке и
C.x = 0x000000000BADF00D;
в другом потоке, то это возможно в третьем потоке:
Console.WriteLine(C.x);
имеют, что выписывают 0xDEADBEEF0BADF00D, хотя логически переменная никогда не удерживала это значение. Язык С# оставляет за собой право сделать запись длинным эквивалентом записи в два ints один за другим, и на практике некоторые чипы реализуют его таким образом. Переключатель потока после первой записи может заставить читателя прочитать что-то неожиданное.
Длинные и короткие: не разделяйте ничего между двумя потоками, не блокируя что-то. Замки только медленны, когда они довольны; если у вас есть проблема с производительностью из-за конкурирующих замков, то исправить любой архитектурный недостаток, ведущий к конкурирующим замкам. Если блокировки не утверждаются и все еще слишком медленны, только тогда вы должны подумать о том, чтобы перейти к опасным методам с низким уровнем блокировки.
Общей методикой с низким уровнем блокировки для использования здесь является, конечно, вызов Threading.Interlocked.Increment
, который делает приращение целого числа гарантированным атомом. (Обратите внимание, однако, что он по-прежнему не дает гарантий относительно таких вещей, как то, что происходит, если каждый из двух потоков выполняет взаимные блокировки двух разных переменных в разное время, а другие потоки пытаются определить, какое инкремент произошло "первым". С# не гарантирует, что все непрерывные последовательности событий просматриваются всеми потоками.)