0

In an application I have a ?DateTime and an int field I need to access (read/write) in multiple threads. I have read up on multiple resources, from which I learned:

  1. That some threads may use outdated values of variables
  2. lock()ing such a variable will solve this
  3. The microsoft docs uses an example in which such a lock() is not used

From 1) and 3) I would conclude that Microsoft's example would suffer from potentially reading old data from balance as the lock is done on balanceLock.

A trimmed version of the example follows:

using System;
using System.Threading.Tasks;

public class Account
{
    private readonly object balanceLock = new object();
    private decimal balance;

    public Account(decimal initialBalance) => balance = initialBalance;

    public void Credit(decimal amount)
    {
        if (amount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
        }

        lock (balanceLock)
        {
            balance += amount;
        }
    }

    public decimal GetBalance()
    {
        lock (balanceLock)
        {
            return balance;
        }
    }
}

class AccountTest
{
    static async Task Main()
    {
        var account = new Account(1000);
        var tasks = new Task[100];
        for (int i = 0; i < tasks.Length; i++)
        {
            tasks[i] = Task.Run(() => Update(account));
        }
        await Task.WhenAll(tasks);
        Console.WriteLine($"Account's balance is {account.GetBalance()}");
        // Output:
        // Account's balance is 5400
    }

    static void Update(Account account)
    {
        decimal[] amounts = { 0, 2, 3, 6, 2, 1, 8, 5, 11, 6 };
        foreach (var amount in amounts)
        {
            account.Credit(amount);
        }
    }
}

I end up with the following questions:

  1. Is my conclusion correct that this example would suffer from data possibly not being guaranteerd to be up-to-date? I guess not, but then, why is it the case that the balance variable always contains an up-to-date value, regardless of which thread is working on it? (As it is the balanceLock variable that is being locked, not balance?)
  2. If this is not ensured, what methods can I use to make sure values (e.g. ?DateTime and int) are always up to date between threads? I know about lock() and volatile, but the former cannot be used with value fields, and it appears volatile should only be used as a last resort.
Marco
  • 1
  • 2
  • Some links that you might find helpful: [C# "lock" keyword: Why is an object necessary for the syntax?](https://stackoverflow.com/questions/25068301/c-sharp-lock-keyword-why-is-an-object-necessary-for-the-syntax), [Why do we need to lock and object in C#?](https://stackoverflow.com/questions/40444044/why-do-we-need-to-lock-and-object-in-c/40444119), [Threading in C# - Locking](http://www.albahari.com/threading/part2.aspx#_Locking) – Theodor Zoulias Dec 17 '21 at 00:08
  • 1
    I found the answer by reading up on your last link, specifically the part on [full fences](http://www.albahari.com/threading/part4.aspx#_Full_fences) found on a different page. It appears that the issue is not so much that one thread can have a value in cpu cache that lags behind the value in main memory, but rather that (single-thread) optimizations can occur that break logic when multi-threading. Full fences prevent this, and amongst others `lock()` introduces such a full fence. Is this interpretation correct? – Marco Dec 17 '21 at 07:52
  • Yeap, that's correct. One more link that you might find interesting is this: [Memory barrier generators](https://stackoverflow.com/questions/6581848/memory-barrier-generators). – Theodor Zoulias Dec 17 '21 at 09:12

0 Answers0