12

In the following example when the "Submit" button is clicked the value of the static variable Count is incremented. But is this operation thread safe? Is using Appliation object the proper way of doing such operation? The questions are applicable to Web form application s as well.

The count always seem to increase as I click the Submit button.

View(Razor):

@{
    Layout = null;
}
<html>

<body>
    <form>
        <p>@ViewBag.BeforeCount</p>
        <input type="submit" value="Submit" />
    </form>
</body>
</html>

Controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.BeforeCount = StaticVariableTester.Count;
        StaticVariableTester.Count += 50;
        return View();
    }     
}

Static Class:

public class StaticVariableTester
{
    public static int Count;
}
fahmi
  • 591
  • 6
  • 27

3 Answers3

13

No, it's not. The += operator is done in 3 steps: read the value of the variable, increase it by one, assign the new value. Expanded:

var count = StaticVariableTester.Count;
count = count + 50;
StaticVariableTester.Count = count;

A thread could be preempted between any two of these steps. This means that if Count is 0, and two threads execute += 50 concurrently, it's possible Count will be 50 instead of 100.

  1. T1 reads Count as 0.
  2. T2 reads Count as 0
  3. T1 adds 0 + 50
  4. T2 adds 0 + 50
  5. T1 assigns 50 to Count
  6. T2 assigns 50 to Count
  7. Count equals 50

Additionally, it could also be preempted between your first two instructions. Which means two concurrent threads might both set ViewBag.BeforeCount to 0, and only then increment StaticVariableTester.Count.

Use a lock

private readonly object _countLock = new object();

public ActionResult Index()
{
    lock(_countLock)
    {
        ViewBag.BeforeCount = StaticVariableTester.Count;
        StaticVariableTester.Count += 50;
    }
    return View();
}   

Or use Interlocked.Add

public static class StaticVariableTester
{
    private static int _count;

    public static int Count
    {
        get { return _count; }
    }

    public static int IncrementCount(int value)
    {
        //increments and returns the old value of _count
        return Interlocked.Add(ref _count, value) - value;
    }
}

public ActionResult Index()
{
    ViewBag.BeforeCount = StaticVariableTester.IncrementCount(50);
    return View();
} 
dcastro
  • 66,540
  • 21
  • 145
  • 155
5

Increment is not atomic so is not thread safe.

Check out Interlocked.Add:

Adds two 32-bit integers and replaces the first integer with the sum, as an atomic operation.

You'd use it like this:

Interlocked.Add(ref StaticVariableTester.Count, 50);

Personally I'd wrap this in your StaticVariableTester class:

public class StaticVariableTester
{
    private static int count;

    public static void Add(int i)
    {
        Interlocked.Add(ref count, i);
    }

    public static int Count
    {
        get { return count; }
    }
}

If you want the returned values (as per dcastro's comment) then you could always do:

public static int AddAndGetNew(int i)
{
     return Interlocked.Add(ref count, i);
}

public static int AddAndGetOld(int i)
{
     return Interlocked.Add(ref count, i) - i;
}

In your code you could do

ViewBag.BeforeCount = StaticVariableTester.AddAndGetOld(50);
dav_i
  • 27,509
  • 17
  • 104
  • 136
4

If a method (instance or static) only references variables scoped within that method then it is thread safe because each thread has its own stack. You also can achieve thread safety by using any variety of synchronization mechanisms.

This operation is not thread safe because it uses shared variable: ViewBag.BeforeCount.

What Makes a Method Thread-safe? What are the rules?

Community
  • 1
  • 1
Alex Erygin
  • 3,161
  • 1
  • 22
  • 22
  • This answer implies an *if and only if* relationship between use of non-local variables and thread safety. However, it is entirely possible to have thread-safe code which references externally scoped variables, through any variety of synchronization mechanisms. – Chris Hayes Apr 03 '14 at 19:18