diff --git a/src/Atom.cs b/src/Atom.cs new file mode 100644 index 0000000..6e22e03 --- /dev/null +++ b/src/Atom.cs @@ -0,0 +1,23 @@ +namespace CSharpPlus; + +public sealed class Atom(T value) where T : class +{ + public T Value => value; + + public void Swap(Func updater) + { + SpinWait sw = new(); + while (true) + { + var curr = value; + var next = updater(curr); + var result = Interlocked.CompareExchange(ref value, next, curr); + if (ReferenceEquals(result, curr)) + break; + + sw.SpinOnce(); + } + } + + public void Reset(T resetValue) => value = resetValue; +} diff --git a/tests/CSharpPlus.Tests/AtomTests.cs b/tests/CSharpPlus.Tests/AtomTests.cs new file mode 100644 index 0000000..5dfd096 --- /dev/null +++ b/tests/CSharpPlus.Tests/AtomTests.cs @@ -0,0 +1,41 @@ +namespace CSharpPlus.Tests; + +public class AtomTests +{ + const int Count = 10_000; + + record Integer(int Value) + { + public Integer Increment() => new(Value + 1); + public Integer Decrement() => new(Value - 1); + + public static implicit operator int(Integer i) => i.Value; + public static implicit operator Integer(int i) => new(i); + } + + [Test] + public void ShouldIncrement() + { + Atom atom = new(0); + + Parallel.For(0, Count * 2, n => + atom.Swap(x => n < Count ? x.Increment() : x.Decrement())); + + atom.Value.Value.Should().Be(0); + } + + [Test] + public async Task ShouldIncrementTask() + { + Atom atom = new(0); + + await Task.WhenAll( + Enumerable.Range(0, Count * 2) + .Select(n => Task.Run(() => + atom.Swap(x => n < Count + ? x.Increment() + : x.Decrement())))); + + atom.Value.Value.Should().Be(0); + } +}