-
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
Interlocked.CompareExchange missing for uint, ulong and (if possible) general structs. #24694
Comments
From @stephentoub on May 18, 2017 20:22
For uint, you could do something like: static unsafe uint InterlockedCompareExchange(ref uint location, uint value, uint comparand)
{
fixed (uint* ptr = &location)
{
return (uint)Interlocked.CompareExchange(ref *(int*)ptr, (int)value, (int)comparand);
}
} For ulong, same but with long instead of int overload. For structs, not sure how that would work given platform limitations on what sizes can be worked with atomically, alignment, etc. |
From @stephentoub on May 18, 2017 20:24 (And with System.Runtime.CompilerServices.Unsafe, you could do the same thing for uint/ulong but without the unsafe pointer stuff: https://github.com/dotnet/corefx/blob/master/src/System.Runtime.CompilerServices.Unsafe/ref/System.Runtime.CompilerServices.Unsafe.cs#L19) |
From @redknightlois on May 18, 2017 21:59 Great! Gonna use that. |
From @danmosemsft on May 20, 2017 2:15 Do we still need an issue? Is there a proposal to add such API? |
From @redknightlois on May 22, 2017 13:20 It feels strange that |
From @stephentoub on May 22, 2017 13:46
I expect it's because they're not CLS compliant. |
@redknightlois is this still relevant to you, do you want to write this up as a formal API proposal, per instructions in /Documentation? |
@redknightlois Just as a bit of clarification regarding the other part of the title: General structs can't be supported with |
I have used the workaround, I am more worried about the Interlocked call being an FCALL (I opened an issue for that too). |
@danmosemsft found a couple more nuisances in the API of interlocked lately. Say you have a struct like this: [StructLayout(LayoutKind.Sequential, Size = 128)]
private struct Container
{
public MemoryBlock* Block1;
public MemoryBlock* Block2;
public MemoryBlock* Block3;
public MemoryBlock* Block4;
} And now you have that in memory and need to swap atomically the content. ref Container bucket = ref _containers[threadId];
Interlocked.CompareExchange(bucket.Block1, null, bucket.Block1); That does not work because there is no Probably there is a workaround but I couldnt find it. Anyway, posting it here because it is kinda related to the general shortcomings of the Interlocked API with primitive types like pointers. PS: Point me to how to write a formal API request and I would do it. |
You can get the
I do not think that this API would actually do what you want in your example. You would have to still wrap it with extra goo using |
Actually no, the workaround doesn't work at all. That's how my examples would look in actual code. Now if (Interlocked.CompareExchange(ref *(IntPtr*)container.Block1, (IntPtr)header, IntPtr.Zero) == (IntPtr)header)
return; The closest thing I could achieve is: var ptr = new IntPtr(container.Block1);
if (Interlocked.CompareExchange(ref ptr, IntPtr.Zero, (IntPtr)header) == IntPtr.Zero)
return; which also doesnt work either, because not ptr is a local variable and it gets swapped instead of the actual reference to Any help would be appreciated, if I cannot make it work, I have to come back to the design board for the entire thing. |
Right, it means that
Try this: fixed (Bucket * b = &bucket)
if (Interlocked.CompareExchange(ref *(IntPtr*)&(b->Block1), IntPtr.Zero, (IntPtr)header) == IntPtr.Zero)
return; Another option is to use private struct Container
{
private IntPtr _block1;
private IntPtr _block2;
private IntPtr _block3;
private IntPtr _block4;
public MemoryBlock* Block1 => (MemoryBlock*)_block1;
public MemoryBlock* Block2 => (MemoryBlock*)_block2;
public MemoryBlock* Block3 => (MemoryBlock*)_block3;
public MemoryBlock* Block4 => (MemoryBlock*)_block4;
....
} Then you can use |
Just to clarify, is the API proposal here that we go through all members of the I assume we'd do the same for |
API proposal: public static partial class Interlocked
{
/* partial list of existing APIs, for reference */
public static int Add(ref int location1, int value) { throw null; }
public static long Add(ref long location1, long value) { throw null; }
public static int CompareExchange(ref int location1, int value, int comparand) { throw null; }
public static long CompareExchange(ref long location1, long value, long comparand) { throw null; }
public static System.IntPtr CompareExchange(ref System.IntPtr location1, System.IntPtr value, System.IntPtr comparand) { throw null; }
public static int Decrement(ref int location) { throw null; }
public static long Decrement(ref long location) { throw null; }
public static int Exchange(ref int location1, int value) { throw null; }
public static long Exchange(ref long location1, long value) { throw null; }
public static System.IntPtr Exchange(ref System.IntPtr location1, System.IntPtr value) { throw null; }
public static int Increment(ref int location) { throw null; }
public static long Increment(ref long location) { throw null; }
public static long Read(ref long location) { throw null; }
/* new proposed APIs, pretty much a carbon copy of the above but unsigned */
/* all of these would be marked [CLSCompliant(false)] */
public static uint Add(ref uint location1, uint value) { throw null; }
public static ulong Add(ref ulong location1, ulong value) { throw null; }
public static uint CompareExchange(ref uint location1, uint value, uint comparand) { throw null; }
public static ulong CompareExchange(ref ulong location1, ulong value, ulong comparand) { throw null; }
public static System.UIntPtr CompareExchange(ref System.UIntPtr location1, System.UIntPtr value, System.UIntPtr comparand) { throw null; }
public static uint Decrement(ref uint location) { throw null; }
public static ulong Decrement(ref ulong location) { throw null; }
public static uint Exchange(ref uint location1, uint value) { throw null; }
public static ulong Exchange(ref ulong location1, ulong value) { throw null; }
public static System.UIntPtr Exchange(ref System.UIntPtr location1, System.UIntPtr value) { throw null; }
public static uint Increment(ref uint location) { throw null; }
public static ulong Increment(ref ulong location) { throw null; }
public static ulong Read(ref ulong location) { throw null; }
} |
namespace System.Threading
{
public static partial class Interlocked
{
// New proposed APIs, pretty much a carbon copy of the above but unsigned
// All of these would be marked [CLSCompliant(false)]
public static uint Add(ref uint location1, uint value);
public static ulong Add(ref ulong location1, ulong value);
public static uint CompareExchange(ref uint location1, uint value, uint comparand);
public static ulong CompareExchange(ref ulong location1, ulong value, ulong comparand);
public static UIntPtr CompareExchange(ref UIntPtr location1, UIntPtr value, UIntPtr comparand);
public static uint Decrement(ref uint location);
public static ulong Decrement(ref ulong location);
public static uint Exchange(ref uint location1, uint value);
public static ulong Exchange(ref ulong location1, ulong value);
public static UIntPtr Exchange(ref UIntPtr location1, UIntPtr value);
public static uint Increment(ref uint location);
public static ulong Increment(ref ulong location);
public static ulong Read(ref ulong location);
// Since these were approved in #23819 we should add them:
// All of these would be marked [CLSCompliant(false)]
public static int And(ref uint location1, uint value);
public static long And(ref ulong location1, ulong value);
public static int Or(ref uint location1, uint value);
public static long Or(ref ulong location1, ulong value);
public static int Xor(ref uint location1, uint value);
public static long Xor(ref ulong location1, ulong value);
}
} |
@stephentoub, we should probably clarify that I'm not sure I like that we added the first two ( |
There are lots of other operations that weren't included. What about Interlocked.Not, for example? Interlocked.Mul? Interlocked.EverythingCsharpHasAnOperatorFor? We should only add methods when there's a demonstrated need. I've never seen C# code that needed Interlocked.Xor; if such a need appears, we can add it then. |
It wasn't part of the API review.
Not supported by most/any hardwar:
I think there is a slight difference when it comes to "primitive" operations. Interlocked operations are some of the most basic operations on top of which other threading/locking algorithms are implemented. |
My point was that there are lots of other things beyond these. And/Or are used. There was no benefit in Xor being included, and we didn't spend time in API review discussing the merits of each individually; we should in the future.
So? This isn't about hardware support. These APIs were checked in without hardware support. We can ship the product without hardware support.
.NET has existed for almost 20 years without these variants. We shouldn't be adding effectively dead code until it'll actually be used. |
I agree. I think we should try to bring these points up in API review so we don't approve and then immediately turn around and not implement them.
While I don't know of any usages in the framework, a search shows there are various existing usages of equivalents from other languages/frameworks. For example
Right, and you don't need anything except CompareExchange support of the appropriate size (to prevent tearing) for most single operations to be implemented. But, I think hardware support is a reasonable baseline for what other languages/frameworks implement and expose. It also represents something that users cannot implement themselves, as we don't have a way for users to emit something like |
Agreed. In this case, when I actually looked at it, I turned around and asked the dev that proposed the API, and he agreed we could do without it.
Sure. But even per your list, And/Or/Xor was incomplete in this regard. If the goal is completeness as compared to some set of hardware primitives, that should be a separate issue. From my perspective, this issue was about something actually being needed, and I see no evidence that C# developers are clamoring for Interlocked.Xor. In contrast, I've seen a multitude of projects implement their own And/Or, including some of ours.
Any in C#? |
Not that immediately popped up in a search. Most of the results were around the C++ intrinsic and grepping code for sequence of
I think that is reasonable. There were others (like atomic bitwise set/complement/reset) which were mentioned during the review that we likewise said should be in a separate "completeness" proposal. |
Most people that need atomic Xor won't be clamoring for it, those that don't have them just disregard certain types of high-performance algorithms that require them. It is a completeness issue, no one will clamor for anything that they cannot implement; most will just throw their hands in the air, and you won't hear a thing. Having said so, I haven't stumbled on an algorithm that uses XOR atomically, but probably the reason is that as there are not many providing implementations of it, because it is a very niche thing today. For the long term, completeness is always best for a platform, but if that delays the current proposal, I would just go with the partial one. |
If you need atomic XOR (or any other bit transformation), you can trivially implement it using |
If they "need" it, they'll implement it (CompareExchange loop... and if they can't implement it, personally I'd prefer they not be trying to use lock-free code and the Interlocked type at all). That's what I meant by clamoring for it. Can you please point to real C# examples where someone implemented it? If not, adding such a method in the name of completeness is just adding functionality that'll need to be implemented/tested/documented/maintained/shipped/taking up space/etc. for zero actual benefit. If/when such need is demonstrated in the future, it can be added. |
If you guys are gonna be adding unsigned variants to |
From @redknightlois on May 18, 2017 19:47
I recently needed to use
Interlocked.CompareExchange
inside a function with the following form:problem is that it can´t be done because the
Interlocked.CompareExchange<T>
requires it to be a class.Also if anyone knows a workaround for it, I would appreciate it.
Copied from original issue: dotnet/coreclr#11723
The text was updated successfully, but these errors were encountered: