Зачем метод Interlocked.Increment возвращает значение, если параметр передается по ссылке
Что проверяют
Понимаете ли вы, что атомарна только сама операция, а любое следующее чтение переменной уже гонка. Правильный ответ не про удобство, а про корректность. Следом часто просят придумать задачу, где без возвращаемого значения не обойтись, или спрашивают про CompareExchange.
Как отвечать
Interlocked.Increment(ref x) атомарно увеличивает значение на единицу и возвращает новое значение, полученное в той же атомарной операции. На x86 JIT обычно превращает это в одну инструкцию lock xadd.
Почему нельзя просто прочитать x после вызова: между инкрементом и чтением другой поток может успеть увеличить счётчик ещё раз. Код Interlocked.Increment(ref counter); var mine = counter; способен дать двум потокам одинаковое mine и пропустить промежуточное значение. Возвращаемое значение уникально для каждого вызова и соответствует именно вашему инкременту. Отсюда типичные применения:
- выдача номеров:
var id = Interlocked.Increment(ref _nextId);даёт каждому потоку свой идентификатор безlock; - «я первый»:
if (Interlocked.Increment(ref _started) == 1)запускает инициализацию ровно один раз; - подсчёт ссылок:
if (Interlocked.Decrement(ref _refCount) == 0) Dispose();освобождает ресурс только тот поток, который ушёл последним; - ограничение параллелизма: по результату инкремента поток сразу видит, превышен ли лимит.
По остальным методам: Add тоже возвращает новое значение, а Exchange и CompareExchange возвращают исходное, и по нему в цикле CAS проверяют, удалась ли замена. Операции Interlocked работают как полный барьер памяти. Для 64-битного счётчика в 32-битном процессе обычное чтение long не атомарно, там нужен Interlocked.Read.
Где спотыкаются
Объясняют возвращаемое значение удобством или отладкой. Без него атомарное «увеличить и узнать результат» просто не написать.
Считают, что после Increment чтение counter вернёт то же число. В многопоточном коде это не так, и баг проявится только под нагрузкой.
Путают, что возвращают разные методы: Increment и Add отдают новое значение, Exchange и CompareExchange старое. Отсюда ошибки на единицу в проверках вроде == 0.