Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this operation thread safe?

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;
}
like image 639
fahmi Avatar asked Apr 03 '14 11:04

fahmi


2 Answers

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();
} 
like image 121
dcastro Avatar answered Oct 11 '22 21:10

dcastro


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);
like image 5
dav_i Avatar answered Oct 11 '22 20:10

dav_i