-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Interlocked.CompareExchange and Interlocked.Exchange Generic Overload with Unmanaged Type Constraint #75385
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @mangod9 Issue DetailsBackground and motivationIt would be useful to have a generic overload for Benchmark of Sample Code The following benchmark of the proposed code for illustrative purposes only. It's based on two competing threads making random
API Proposalnamespace System.Threading;
public static InterlockedExchange
{
//
public static T CompareExchange<T>(ref T location, T value, T comparand)
where T : struct
{
Interlocked.MemoryBarrier();
if (Unsafe.AreSame(ref location, ref comparand))
{
Interlocked.MemoryBarrier();
location = value;
Interlocked.MemoryBarrier();
}
return location;
}
} API Usagepublic enum State
{
Idle,
Running
}
var currentVal = State.Idle;
var newVal = State.Running;
Interlocked.CompareExchange(ref currentVal, newVal, State.Idle);
Alternative DesignsNo response RisksUncertain of all types under the
|
Methods of the |
Here's a (likely) correct implementation: using System.Runtime.CompilerServices;
public static class InterlockedNeo
{
public static T CompareExchange<T>(ref T location, T value, T comparand) where T : unmanaged => Unsafe.SizeOf<T>() switch
{
// 1 => Unsafe.As<byte, T>(ref Unsafe.AsRef(Interlocked.CompareExchange(ref Unsafe.As<T, byte>(ref location), Unsafe.As<T, byte>(ref value), Unsafe.As<T, byte>(ref comparand)))),
// 2 => Unsafe.As<ushort, T>(ref Unsafe.AsRef(Interlocked.CompareExchange(ref Unsafe.As<T, ushort>(ref location), Unsafe.As<T, ushort>(ref value), Unsafe.As<T, ushort>(ref comparand)))),
4 => Unsafe.As<int, T>(ref Unsafe.AsRef(Interlocked.CompareExchange(ref Unsafe.As<T, int>(ref location), Unsafe.As<T, int>(ref value), Unsafe.As<T, int>(ref comparand)))),
8 => Unsafe.As<long, T>(ref Unsafe.AsRef(Interlocked.CompareExchange(ref Unsafe.As<T, long>(ref location), Unsafe.As<T, long>(ref value), Unsafe.As<T, long>(ref comparand)))),
_ => throw new NotSupportedException("This type cannot be handled atomically")
};
public static T Exchange<T>(ref T location, T value) where T : unmanaged => Unsafe.SizeOf<T>() switch
{
// 1 => Unsafe.As<byte, T>(ref Unsafe.AsRef(Interlocked.Exchange(ref Unsafe.As<T, byte>(ref location), Unsafe.As<T, byte>(ref value)))),
// 2 => Unsafe.As<ushort, T>(ref Unsafe.AsRef(Interlocked.Exchange(ref Unsafe.As<T, ushort>(ref location), Unsafe.As<T, ushort>(ref value)))),
4 => Unsafe.As<int, T>(ref Unsafe.AsRef(Interlocked.Exchange(ref Unsafe.As<T, int>(ref location), Unsafe.As<T, int>(ref value)))),
8 => Unsafe.As<long, T>(ref Unsafe.AsRef(Interlocked.Exchange(ref Unsafe.As<T, long>(ref location), Unsafe.As<T, long>(ref value)))),
_ => throw new NotSupportedException("This type cannot be handled atomically")
};
} Supporting |
Understood, it would be great to have framework support for more complex atomic operations - similar to those Java provides at the library level. My apologies as I'm not a .Net Framework expert and lack sufficient knowledge of the BCL, threading, compiler, etc. (as evidenced by the use of Will leave it to the experts from here. using System.Runtime.CompilerServices;
using System.Threading;
public static class InterlockedEx
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe static T CompareExchange<T>(ref T current, T value, T compareand)
where T : unmanaged
{
Thread.MemoryBarrier();
if (IsEqual((byte*)Unsafe.AsPointer(ref current), (byte*)Unsafe.AsPointer(ref compareand), Unsafe.SizeOf<T>()))
Exchange(ref current, value);
return current;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static bool IsEqual(byte* src, byte* dst, int length)
{
for (int i = 0; i < length; i++)
{
if (*(src + i) != *(dst + i))
{
return false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Exchange<T>(ref T location, T value)
where T : unmanaged
{
Thread.MemoryBarrier();
location = value;
Thread.MemoryBarrier();
return location;
}
} |
Sorry, they are still not atomic (and the implementations are wrong, they have to return the old value). Imagine you have the following code. Guid g = default;
Interlocked.Exchange(ref g, Guid.NewGuid()); When it does Atomicity in the methods of the |
Also, even ignoring the size issue, the methods as proposed are not atomic: |
Thanks for the clarification. I was under the impression |
Even if it does, that's not sufficient: reads on multiple threads can happen before any write (paraphrasing):
(assuming an otherwise working version that attempts to return the old values) Thread B should be returning |
Background and motivation
It would be useful to have a generic overload for
Interlocked.Compare
andInterlocked.CompareExchange
with a constraint of "unmanaged" for global support of structs, etc...Benchmark of Sample Code
The following benchmark of the proposed code for illustrative purposes only. It's based on two competing threads making random
CompareExchange
assignments to the same static variable 10M times (~5M each)...Linqpad Benchmark Script
API Proposal
API Usage
Alternative Designs
No response
Risks
Uncertain if all types under the
unmanaged
constraint are supported byUnsafe.AreSame
in sample code. Just a prototype for illustrative purposes.The text was updated successfully, but these errors were encountered: