0

Although I have read already a lot through docs and forums I am still not clear about data caching and visibility of data shared among threads.

I am clear about updating values shared by different threads using lock statement.

As an example:

public class Foo
{
  object locker = new object();

  int someValue;
  
  public void SetValue (int value)
  {
    lock (locker)
    {
      someValue = value;
    }
  }

  public int GetValue()
  {
     int value ;
     lock(locker)
     {
       value = someValue;
     }
     return value;
   }
 }

But now lets get a little bit deeper, where my problem is.

Think of a class named Person which is predefined and cannot be modified (for example it is coming from another assembly)

A class instance shall be passed between two threads.

public class Person
{
  public string Name { get; }
  public string SirName { get; }
  public uint Age { get; }
  public bool EntryValid { get; }

  public Person(string name, string sirName, uint age)
  {
    Name = name;
    SirName = sirName;
    Age = age;
    EntryValid = true;
  }

  public Person()
  {
    EntryValid = false;
  }
}

public class FoundResult
{
  private object locker;
  private Person person;
  private bool result;

  public FoundResult ()
  {
    locker = new object();

    // Is lock required here?
    result = false;
  }

  // Called from Thread No 1
  public void PlaceResult(Person person)
  {
    lock (locker)
    {
      this.person = person;  // This may be extended by an additional Clone(); 
      result = true;
    }
  }

  // Called from Thread No 2
  public bool HasResult()
  {
    bool result;

    lock (locker)
    {
      result = this.result;
    }
    return result;
  }

  // Called from Thread No 2
  public Person GetResult()
  {
    Person person;
    lock (locker)
    {
      person = this.person; // This may be extended by an additional Clone(); 
      result = false;
    }
    return person;
  }
}

To reduce overloading the example I assume that the threads will be signaled properly so that they will know when to access the methods. Then it will goes like this::

Thread no 1 is going to be asked to search for a person (for instance by a list which is held into the threads space) and places the result by calling PlaceResult. The thread will be going to signal Thread no. 2 which is going to call GetResult().

The question is now about the visibility of the data stored in person instance. Does lock guarantee that the values (name, age etc.) in person are passed from thread no 1 to no 2?

Or is it still possible that both threads only work on their caches or registers and thread no 2 reads sometimes the same "old" data?

And if so, how do I manage that the data are properly passed without touching the Person class itself?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
MrV
  • 25
  • 5
  • 2
    Highly relevant: [Memory barrier by lock statement](https://stackoverflow.com/q/2844452/15498). From accepted answer: "The lock keyword creates full-fences on both boundaries" – Damien_The_Unbeliever Jan 20 '21 at 11:41
  • 1
    If you always use lock when you accessing shared object from 2 or more threads everything is OK. I think that edited object is read from memory. – Lazar Đorđević Jan 20 '21 at 11:43
  • 1
    @Theodor Zoulias Thanks for your hint! You're correct! I am going to change that – MrV Jan 20 '21 at 12:17
  • The code is OK now, I am deleting my previous comment. Regarding the code-comment `// Is lock required here?`, you could take a look at this question: [Should thread-safe class have a memory barrier at the end of its constructor?](https://stackoverflow.com/questions/38881722/should-thread-safe-class-have-a-memory-barrier-at-the-end-of-its-constructor) Spoiler alert, to this day that question has not received a satisfactory (official) answer. – Theodor Zoulias Jan 20 '21 at 12:36
  • Somewhat related: [Thread Safe Properties in C#](https://stackoverflow.com/questions/7161413/thread-safe-properties-in-c-sharp) – Theodor Zoulias Jan 20 '21 at 12:43

1 Answers1

1

Does lock guarantee that the values (name, age etc.) in 'person' are passed from thread no 1 to no 2.

Yes. Lock implies a full memory barrier both when entering and leaving.

Or is it still possible that both threads only work on their caches or registers and thread no 2 reads sometimes the same "old" data.

No. Since there is a memory barrier both threads will be forced to read the actual value from memory.

The great thing about locks is that they are reasonably safe. As long as a shared field is only accessed from inside a lock it should be safe. It might be possible to get a correct behavior in your example using Interlocked or volatile. But if it is not performance critical you might as well use a lock and be safe.

// Is lock required here? result = false;

No, this is called in the constructor. While inside the constructor the object can only possibly be accessed by the thread that creates the object. At least as long as the constructor does not give another thread a reference to itself. As far as I know any other threads that accesses the object would need to load it from memory at least once. This ventures into technical details I'm not sure about, but I would expect the GC to issue at least a memory barrier when reclaiming memory, thus ensuring the other threads cannot not observe an reclaimed object when a memory address is reused.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thank you for your answer.. In conclusion, when Person class would have one more variable of any reference type, which has an unknown amount of value types or even more reference types, everything is flushed into main memory or comes from it as long on top anywhere where 'Person person' is accessed by (the same) locker object? What about the first initialisation after locker has been created. Do I need to fence 'result = false' first tim , too? – MrV Jan 20 '21 at 12:27