20

I want to increment an unsigned integer from multiple threads.

I know about Interlocked.Increment, but it does not handle unsigned integers. I could use lock(), but I would rather not if possible for performance reasons.

Is it thread safe just to increment it in the normal way? It would not matter if the occasional increment got lost, as it's only used for statistics. What I don't want is the value to get corrupted.

Mike Schenk
  • 1,512
  • 9
  • 21
FlappySocks
  • 3,772
  • 3
  • 32
  • 33
  • This can be done with ILEmit to create any method of the Interlocked class work with any blittable type of 32 or 64 bits, there is an answer describing that somewhere on SO... – AnorZaken Feb 19 '19 at 12:01

6 Answers6

45

You say you don't want to use lock for performance reasons - but have you tested it? An uncontested lock (which this is likely to be, by the sounds of it) is pretty cheap.

I generally go for "obviously correct" rather than "clever and possibly better performing" when it comes to threading (and in general, but especially for threading).

Benchmark your app with and without locking, and see whether you can even notice the difference. If locking makes a significant difference then sure, use cunning stuff. Otherwise, I'd just stick with a lock.

One thing you might want to do is use Interlocked.Increment with an int and just cast it when necessary to get a uint, like this:

using System;
using System.Reflection;
using System.Threading;

public class Test
{
    private static int count = int.MaxValue-1;

    public static uint IncrementCount()
    {
        int newValue = Interlocked.Increment(ref count);
        return unchecked((uint) newValue);
    }

    public static void Main()
    {
        Console.WriteLine(IncrementCount());
        Console.WriteLine(IncrementCount());
        Console.WriteLine(IncrementCount());
    }

}

Output:

2147483647
2147483648
2147483649

(In other words it wraps with no problems.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I agree why complicate things unless performance actually becomes an issue. I can't imagine a lock for incrementing a single value is going to remain for very long or use much resources. – PeteT Jun 01 '09 at 12:54
  • 1
    Doesn't the cast (dangerously) assume an 'unchecked' context? We want to reinterpret the bits of the 2's complement signed int, as a uint, without range checks. For this shouldn't we explicitly do `return unchecked((uint)newValue);` ? https://stackoverflow.com/a/1131851/ Incidentally, I can't see any explicit mention of 2's complement in the C# spec, but I presume we can rely on it. – Max Barraclough May 21 '18 at 09:43
  • 1
    @MaxBarraclough: Yes, it would be better to be explicitly unchecked. Will make that change. And yes, you can rely on 2's complement although it's not in the spec right now. We want to include it in a later revision :) – Jon Skeet May 21 '18 at 13:01
11

If you really need the full range of an unsigned int (2^32 - 1) rather than a signed int (2^31 -1), you could cast to an int64 (there is an Interlocked.Increment overload that takes int64) and then cast back to an unsigned int.

Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • 4
    Note though that Interlocked.Increment(ref long) is only actually atomic under a 64-bit OS (see documentation) – jerryjvl Jun 01 '09 at 12:37
  • 2
    Personally I'd make do with the regular int for general safety... if 31 bits is not enough, then I doubt 32 will actually work much better, unless the problem is specifically restricted exactly between 2G and 4G measurements. – jerryjvl Jun 01 '09 at 12:39
  • @Jerryjvl - Reminds me of the days of 16bit calculations in 8bit systems. – ChrisBD Jun 01 '09 at 12:48
  • In fact there is no range reduction, as described in my answer. – pre-kidney Sep 25 '18 at 06:22
  • @jerryjvl there is no mention in the [documentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment) about `Interlocked.Increment(Int64)` not being atomic on a 32-bit OS. – Theodor Zoulias Dec 01 '19 at 18:15
3

Building on pre-kidney's answer, you can create your own helper class. Since the increment will work in the same way on a binary level, you can just change the type from unsigned to signed before incrementing with the Unsafe class:

using System.Runtime.CompilerServices;
using System.Threading;

public static class InterlockedEx
{
    /// <summary>
    /// unsigned equivalent of <see cref="Interlocked.Increment(ref Int32)"/>
    /// </summary>
    public static uint Increment(ref uint location)
    {
        int incrementedSigned = Interlocked.Increment(ref Unsafe.As<uint, int>(ref location));
        return Unsafe.As<int, uint>(ref incrementedSigned);
    }

    /// <summary>
    /// unsigned equivalent of <see cref="Interlocked.Increment(ref Int64)"/>
    /// </summary>
    public static ulong Increment(ref ulong location)
    {
        long incrementedSigned = Interlocked.Increment(ref Unsafe.As<ulong, long>(ref location));
        return Unsafe.As<long, ulong>(ref incrementedSigned);
    }
}
Bruno Zell
  • 7,761
  • 5
  • 38
  • 46
3

Since .NET 5.0 there are Interlocked.Increment overloads for both unsigned and signed int32 and int64.

https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment?view=net-5.0

Martin Žid
  • 289
  • 3
  • 18
-1

On systems using a twos complement representation of signed integers ("virtually all", according to wikipedia) incrementing an unsigned integer has the same effect as incrementing a signed integer represented using the same set of bits. Thus, one can use InterlockedIncrement on unsigned integers without sacrificing anything.

For example, with 3 bits we have the following table:

raw bits | unsigned integer | twos complement signed integer
------------------------------------------------------------
000      |                0 |                             0 
001      |                1 |                             1 
010      |                2 |                             2 
011      |                3 |                             3 
100      |                4 |                            -4 
101      |                5 |                            -3
110      |                6 |                            -2
111      |                7 |                            -1

Incrementing by one (and taking overflow into account) is equivalent to moving down one entry in the table, in both cases. Note that this doesn't work for ones complement arithmetic, since the negatives are arranged in the opposite order.

pre-kidney
  • 193
  • 9
  • "Thus, one can use InterlockedIncrement on unsigned integers without sacrificing anything." - no you can't because Interlocked.Increment() takes either Int32 or Int64- both are signed. See Jon Skeet's answer from 9 years ago....Just because something should be possible does not make it so. We know how 2's complement works. Your answer does not address the question. – Mitch Wheat Sep 25 '18 at 06:31
  • The point of this answer is that one can perform the necessary casts without loss of information. – pre-kidney Sep 25 '18 at 06:35
  • you mean like was demonstrated by jon skeet 9 years ago? I fail to see what your answer adds. – Mitch Wheat Sep 25 '18 at 06:36
  • His answer is a recommendation against solving the problem asked by the questioner, and he mentions that wraparound is fine as a side remark at the end. My answer explains what the conditions are to make this true, and why it is so. – pre-kidney Sep 25 '18 at 06:38
  • jon skeets answer states : "(In other words it wraps with no problems.)" – Mitch Wheat Sep 25 '18 at 06:39
  • Correct, although that is not the main thrust of his answer, and it is not explained why this is the case - just that it was verified empirically on his computer... in any case if you don't see the value of my answer, I don't think there is any more I can say that would convince you of it. – pre-kidney Sep 25 '18 at 06:42
-15

you can declare the uint as volatile.

http://msdn.microsoft.com/en-us/library/x13ttww7(VS.71).aspx

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
Sesh
  • 5,993
  • 4
  • 30
  • 39
  • 8
    That only makes sure that any changes to the variable are written back to memory immediately, instead of waiting until either the thread yields or the value is displaced in a register. It does not protect access to the field, i.e. two (or more) threads may read the same value, increment once, and write back, effectively increasing the value by 1. – Cecil Has a Name Jun 01 '09 at 12:42
  • Please read the requirements above and also the example at http://msdn.microsoft.com/en-us/library/7a2f3ay4.aspx. For the given case it is sufficient. – Sesh Jun 01 '09 at 15:00
  • How performant is volatile compared to Interlocked.Increment? Same? Better? – jrista Jun 07 '09 at 02:08
  • @jrista: Perhaps not the most definitive benchmark but on my machine starting 8 tasks on 8 CPU cores incrementing the counter in a tight loop with a `Thread.Sleep(0)` in the loop resulted in the same performance for `volatile` versus `Interlocked.Increment`. Using `lock` was several times slower. – Martin Liversage Oct 23 '13 at 09:34
  • @Martin: Thread.Sleep is an unreliable timing mechanism, though. It's resolution is far larger in granularity than either volatile or Interlocked.Increment, and the actual delay caused by calling Thread.Sleep is really up to the Windows thread scheduler (and is usually on the order of several milliseconds at least.) – jrista Oct 23 '13 at 19:13
  • 7
    This answer looks wrong to me. `volatile` would work when assigning. But not for incrementing. Incrementing requires two operations. `volatile` won't prevent other threads from interposing between the two operations. For this to be safe, you either need an atomic increment (fetch-and-add), like with Interlocked.Increment(), or you need to synchronize access with locking. – Nikos C. Jan 08 '14 at 09:05