-2

In a multithreaded environment, locking on thread-sensitive resources is important. I often assume collections and such are thread-unsafe, depending on MS documentation, but are simple types thread sensitive too?

Let's take examples. Is it useful to lock int properties access, e.g.

public int SomeProperty
{
    get
    {
        lock (_lock)
        {
             return _value;
        }
    }
}

or is a normal getter enough, i.e

public int SomeProperty => _value;

As I understand it, a simple field read is thread safe, but I still see on the web and in some code repositories the first example.

Second question, are values in a single line instruction read sequentially or simultaneously? In other words, do I need to lock when I do that

public TimeSpan GetSomeExampleValue()
{
    lock (_lock)
    {
        return _dateTime1 - _dateTime2;
    }
}

or can I simply do

public TimeSpan GetSomeExampleValue()
{
    return _dateTime1 - _dateTime2;
}
Shay
  • 183
  • 2
  • 13
  • You may want to have a look into https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked?view=net-5.0 – Fildor Jul 29 '21 at 13:07
  • For the last example, how I would do it would depend on intended usage. That is: Will it be the case that changes to `_dateTime1` and `_dateTime2` are much for frequent than invocations of the `GetSomeExamplValue` or vice versa? Background: having a lot of threads you may probably want to minimize collisions. – Fildor Jul 29 '21 at 13:09
  • 1
    This article might be interesting for you: https://ericlippert.com/2014/03/12/can-i-skip-the-lock-when-reading-an-integer/ – SomeBody Jul 29 '21 at 13:15
  • 1
    There was an excellent series of articles in MSDN Magazine a few years ago that addresses these concerns. It's a bit old now (being from 2012), but still a good read: https://learn.microsoft.com/en-us/archive/msdn-magazine/2012/december/csharp-the-csharp-memory-model-in-theory-and-practice – Jack A. Jul 29 '21 at 13:35
  • @Shay it seems that you could benefit by studying multithreaded more systematically. Here is a valuable online resource: [Threading in C#](http://www.albahari.com/threading/) by Joseph Albahari. There are also paper books that you could buy. – Theodor Zoulias Jul 29 '21 at 18:16

1 Answers1

5

Note: everything here is predicated on valid locking; lock against an int (lock (_value) in the first example) is a very bad thing and provides zero protection; don't do that! The compiler is probably screaming at you already (you should only lock against reference-types, otherwise it boxes each time and gives you a different object, and therefore a different lock each time).

As I understand it, a simple field read is thread safe,

It is much more complicated than that. First, you need to define thread safety! There are three different scenarios that you might mean:

  1. avoiding "torn" values - an inconsistent single value that was read while a write was happening and has a mangled state that is (typically) half from one of the before/after values, and half from the other - creating a third phantom value that never logically existed; here, we need to think about "atomicity" - the C# language defines types that will always be atomic safe, which includes int and references, but which does not include DateTime; as such, the int example is safe here, but the DateTime value is not (however, in practice, in a 64-bit process you should be OK for structs up to 64-bit, but: this is not guaranteed by the C# specification - it is mentioned in the CLR specification, though)
  2. avoiding inconsistent states between multiple values; consider again the DateTime value - even if we don't tear anything, we could get _dateTime1 from one side of an update and _dateTime2 from the other side, meaning that the TimeSpan we return is a phantom value that does not represent any consistent logical state of the two fields
  3. internal CPU optimizations such as out-of-order reads/writes, meaning that we get really really odd results

Now

  • without the lock, int is still immune to tearing, but susceptible to the others; DateTime is susceptible to all three
  • with the lock, both are fully guarded against all three

You may also wish to consider immutability as a way of simplifying logic, but keep in mind that readonly is a lie - more of a guideline than an actual rule.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I am sorry, for the first example, I meant `lock (_lock)` not `lock (_value)`. So you're telling me I should use the lock even for reading int value? – Shay Jul 29 '21 at 13:41
  • @Shay The short answer to "are simple types thread sensitive" is "yes". For an `int`, the use of `System.Threading.Interlocked` is probably more appropriate than `lock`. – Jack A. Jul 29 '21 at 13:49
  • 1
    @Shay it is impossible to answer that without a great deal of discussion about the specific scenario and what you're trying to guard against. There are no simple answers in concurrency. – Marc Gravell Jul 29 '21 at 14:09
  • AFAIK even though C# doesn't guarantee it, the CLR *does* guarantee 64-bit atomicity on 64-bit processors (so long as you use the CLR and not some other runtime obviously) – Charlieface Jul 29 '21 at 15:45
  • @Charlieface indeed - 12.6.6 in ECMA 335v5; clarified wording – Marc Gravell Jul 29 '21 at 15:56