-4

The fantastic Interlocked class provides this overload of Increment:

Interlocked.Increment(ref int loc); 

This looks really close to what I am looking for. Only, all I have is not the variable itself, but a pointer to it. So I cannot use ref but would need an overload as such:

// does not exist: 
Interlocked.Increment(int* loc); 

Is there a workaround? Any other way to efficiently and thread safe increment a value via its address in C#?

Haymo Kutschbach
  • 3,322
  • 1
  • 17
  • 25
  • Why not using a `SemaphoreSlim` for this synchronization? – dymanoid Sep 12 '17 at 14:08
  • So, to be clear, you're looking to atomically decrement the *value pointed to by your pointer*, not the *pointer itself*? – Damien_The_Unbeliever Sep 12 '17 at 14:10
  • Also, how is the main thread going to "wait" on this variable becoming decremented to 0 without incurring huge costs itself such that you know you can do that bit safely but somehow need to rule out using e.g. `Barrier`? – Damien_The_Unbeliever Sep 12 '17 at 14:12
  • @Damien_The_Unbeliever correct. Sorry for being misleading in my question. – Haymo Kutschbach Sep 12 '17 at 14:12
  • @dymanoid the goal was to go without any 'new'. `SemaphoreSlim` is a class and needs to be cleaned up after use, no?` – Haymo Kutschbach Sep 12 '17 at 14:14
  • 3
    Instead of crating your own syncronisation prmitive, have you considered using [CountdownEvent](https://msdn.microsoft.com/en-us/library/system.threading.countdownevent(v=vs.110).aspx)? This is really starting to sound like a [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem), update your question explaining what you are doing that you require writing a synchronization primitive with no allocations, there may be better solution. – Scott Chamberlain Sep 12 '17 at 14:14
  • @Damien_The_Unbeliever the main thread can simply read the local directly and, let's say: spin wait on it. The local will only once be '0' - at the end. – Haymo Kutschbach Sep 12 '17 at 14:15
  • 2
    You cannot run a (real) .NET application without a single `new`, so what's the problem? You have to create only one synchronization object for all threads. Either a `Semaphore` or a `CountdownEvent`. – dymanoid Sep 12 '17 at 14:16
  • 4
    So, you're willing to incur the cost of the main thread wasting thousands of cycles spinning on that variable but unwilling to incur the costs of allocating a small object which usually will not take thousands of cycles? – Damien_The_Unbeliever Sep 12 '17 at 14:16
  • All such synch objects (and any `new`) is too much overhead _inside tight, long running loops_. It is not the overhead of `new` but its consequences: GC ;) – Haymo Kutschbach Sep 12 '17 at 14:23
  • You cannot avoid at least some GC cycles anyway. What are you trying to do? If your application needs such speed that a random GC cycle throws it off, you need to use C++ or assembly. I'm 99% sure you're looking at the wrong solution, one way or another. – xxbbcc Sep 12 '17 at 14:26
  • @xxbbcc 'You cannot avoid at least some GC cycles anyway.' Why that? you can do very easily: by not doing any allocations. – Haymo Kutschbach Sep 12 '17 at 14:28
  • Don't create synchronization objects in tight loops. Reuse these objects you can create in advance. – dymanoid Sep 12 '17 at 14:28
  • 1
    @HaymoKutschbach Good luck with that in a .NET application. :) – xxbbcc Sep 12 '17 at 14:29
  • 1
    Closing over a single int to be shared between threads will not cause multiple allocations (or any if the variable is hoisted into a static field) so it's not clear why you want to avoid that? Have you looked at the generated IL to see how the closure is generated? – Lee Sep 12 '17 at 14:32
  • @dymanoid right. Caching might work. Not sure if they support reusing, though? – Haymo Kutschbach Sep 12 '17 at 14:34
  • @Lee Did not look at the IL yet. But so far I thought the displayclass is instantiated every time the closure is hit / the function defining the closure is called? – Haymo Kutschbach Sep 12 '17 at 14:36
  • wow! there might be better solutions to the actual problem, but why the downvotes on the question? – Haymo Kutschbach Sep 12 '17 at 14:39
  • @HaymoKutschbach I didn't downvote but I think the reason others did is because you don't explain what you're trying to do - you're just sticking to "no 'new`" and "no GC" which are not realistic in a .NET program and it's very likely you're not attempting the right solution. Just a guess, though. – xxbbcc Sep 12 '17 at 14:41
  • @xxbbcc thanks for your suggestion. I edited the question (and pot. will remove the reasoning since it seems to distract people from the question) – Haymo Kutschbach Sep 12 '17 at 14:49
  • funny, I wonder what is so wrong with the question to break my personal record on downvotes: 'thread safe', 'pointer', 'increment'...? :) – Haymo Kutschbach Sep 12 '17 at 14:58
  • This is getting multiple re-open votes; however I don't think an answer can exist. – Joshua Sep 12 '17 at 18:12
  • @Joshua an answer that says "this is not possible, here's why" is a valid answer – bennofs Sep 12 '17 at 18:14

2 Answers2

1

So we could do internal static extern Increment(ref int* loc); easily enough; the corresponding native function looks writeable:

int *Increment(int **loc)
{
    return (int*)InterlockedAdd((int *)loc, sizeof(int));
}

But you can't use it. Your resulting code would look something like:

int *locus = Interlocked.Increment(loc);
if (locus < base + length)
{
    // do something with locus
}

But this is actually undefined. If loc gets incremented too many times it could overflow and locus ends up pointing at a really low address. (base might be right at the top of user memory ...).


On the other hand if you have a pointer to the integer you want to increment; just P/Invoke the call to InterlockedIncrement already. Oops; not on 64 bit; would have to build a tiny C dll to pick up the intrinsic.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • PInvoke sounds promising. I did this and it is working - on 32bit. I am still struggling with 64bit, though. There is no such export 'InterlockedDecrement' or 'InterlockedExchangeAdd' (which seems to be used by Intelocked.Decrement, really) in the 64bit kernel32.dll. Google says that this is always done as intrinsic. Not sure, how this is working for 64bit .NET. Maybe some CLR trickery? – Haymo Kutschbach Sep 12 '17 at 22:59
  • @HaymoKutschbach: They must have a native function that does it then. – Joshua Sep 12 '17 at 23:35
  • Here is a related question regarding [PInvoking InterlockedDecrement](https://stackoverflow.com/questions/5339277/unable-to-find-an-entry-point-named-interlockedincrement-in-dll-kernel32-dll) in kernel32.dll. Unless there is another `InterlockedDecrement` left over for compatibility in some dll(?) for 64 bit a small unmanaged C helper dll as suggested by @Joshua seems the way to go. One might decide to save the dll and use a byte[] array with the x64 assembly instruction(s) to construct a delegate (unmanaged function pointer) at runtime. (Not that I would ever suggest such a risky venture...) – Haymo Kutschbach Sep 13 '17 at 09:40
0

EDIT: The actual answer to the question is much more simple. Supposing that p is the int* to the variable to be decremented. At least the C# compiler allows ref on the dereferenced p directly:

Threading.Interlocked.Decrement(ref (*p)); 

And when coming from

int local = 10; 
IntPtr p = (IntPtr)(&local); 
... 
// decrement local via p
Threading.Interlocked.Decrement(ref (*(int*)p)); 

Note that it is important to use the interlocked class (Interlocked.Decrement) since the JIT produces efficient code for it. (But not as efficient as using ref local directly, though...)


This part of the answer is now outdated. However, I leave it here since the technique of wrapping a local into a struct might be helpful anyways...

This answer is applicable only if you have the option to modify the code and the semantic of the 'pointer'. Exactly spoken, it does not answer the original question but should be helpful nevertheless.

Besides the option to P/Invoke a native function (and its disadvantages: P/Invoke overhead, unmanaged function maintenance overhead) there is another option. It enables the direct use of the Interlocked class. The JIT compiler will generate more efficient code for it and we save the P/Invoke calls.

The solution is inspired by this thread and actually a shameless copy of the answer presented by Mike Danes:

https://social.msdn.microsoft.com/Forums/en-US/b9f9e359-3ca9-43c1-9870-fe562794fca8/interlockedcompareexchange-of-unmanaged-memory-from-vb?forum=clr&prof=required

The following code demonstrates how to decrement a local, stack allocated int counter atomically from multiple threads. It gets away without any additional objects on the managed heap and without any more advanced synchronization objects. Please make sure to secure this approach against the common risks before applying to production code.

internal struct DownCounter {
    internal int value;

    public DownCounter(int initialValue) {
        value = initialValue;
    }

    public int Decrement() {
        return Interlocked.Decrement(ref value);
    }
}

unsafe class Program {
    static void Main(string[] args) {
        // the local stack allocated counter, to be shared by all threads
        DownCounter counter = new DownCounter(10);

        // some work for the threads
        Action<object> work = (c) => {
            double a = 1;
            for (int i = 0; i < 1 << 26; i++) {
                // do stuff here 
                a = a % i * a % (i + 1);
            }
            // decrement the main thread's stack counter
            int d = (*((DownCounter*)(IntPtr)c)).Decrement();
            Console.WriteLine($"Decremented: {d}");
        };

        // start 10 threads, each decrements the local, stack allocated(!) counter
        for (int i = 0; i < 10; i++) {
            Task.Factory.StartNew(work, (IntPtr)(&counter));
        }

        // poor mans 'spin wait' on the local counter
        while (counter.value > 0) {
            Thread.Sleep(100);
            Console.WriteLine($"# threads running: {counter.value}");
        }
        Console.Read();
    }
}

Instead of passing around the int* pointer itself, we now encapsulate the value to be decremented into a small struct and pass the pointer to the struct. In order to decrement the value inside the struct atomically we dereference the pointer to call a helper function Decrement() of the struct. This simply calls Interlocked.Decrement on the wrapped value.

user492238
  • 4,094
  • 1
  • 20
  • 26
Haymo Kutschbach
  • 3,322
  • 1
  • 17
  • 25