Skip to content

Volatile vs Interlocked vs lock in C#: .NET 10 and a harness that races - #2276

Open
vladimir-pecanac-main wants to merge 2 commits into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/96092-csharp-volatile-interlocked-lock
Open

vladimir-pecanac-main wants to merge 2 commits into
CodeMazeBlog:mainfrom
vladimir-pecanac-main:seo/96092-csharp-volatile-interlocked-lock

Conversation

@vladimir-pecanac-main

Copy link
Copy Markdown
Collaborator

Updates the sample for the article "Volatile vs Interlocked vs lock in C#" (https://code-maze.com/csharp-volatile-interlocked-lock/). Only csharp-advanced-topics/VolatileInterlockedLockInCsharp changes.

  • Both projects move from net7.0 to net10.0.
  • Packages, re-queried on NuGet on 2026-10-10 (latest stable): Microsoft.NET.Test.Sdk 18.10.1, xunit 2.9.3, xunit.runner.visualstudio 4.0.1, coverlet.collector 10.1.0. The folder keeps xunit.
  • AccountService.WithdrawBalance now runs ten workers doing one hundred withdrawals each (the same 1,000 withdrawals of 100 from 100,000) instead of 1,000 one-shot tasks that each slept a random 50 to 300 ms. The sleep spread the tasks out so far that the race rarely happened and the unsynchronized and volatile runs often printed the correct 0. On the new harness, ten consecutive runs on .NET 10.0.10 never printed 0 unsynchronized or under volatile, and lock and Interlocked printed 0 in all ten. The test suite drops from about 40 seconds to well under a second.
  • private object _lock = new object(); becomes private readonly Lock _lock = new(); (System.Threading.Lock, .NET 9 and later).
  • New Account.WithdrawIfSufficient (an Interlocked.CompareExchange retry loop that never overdraws) and a new OneShotInitializer (Interlocked.Exchange as a one-shot gate), both demonstrated in Program.cs and covered by a test.
  • The two race tests asserted NotEqual(100000, ...) and NotEqual(1000, ...), which can never fail. Asserting that the race happened would be flaky, because a race is not guaranteed to occur, so they are renamed to ...ThenFinalBalanceIsNotGuaranteed and assert the invariant that always holds: the balance stays between 0 and 100,000.

Build: 0 warnings, 0 errors. Tests: 6 of 6 passing. dotnet list package --vulnerable --include-transitive: no vulnerable packages. SDK 10.0.302, runtime 10.0.10.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant