[Mono-devel-list] Thread safety of readonly data members?

Michi Henning michi at zeroc.com
Wed Feb 18 14:24:49 EST 2004


Jonathan Pyor wrote:

> I would think whether or not the lock is required, in either
> environment, would depend on how you create your class instances.  Do
> you allow your code to possibly invoke both the constructor and the
> method at the same time?

No -- construction and access can't overlap. getVal() is called by
another thread some time after construction completes.

> So no, the lock is NOT necessary in C++.

Uh, sorry, but that's not correct. On SMP machines, things can happen
in the following order:

- Thread 1, running on CPU 1, reads a variable. This loads a cache line
  on CPU 1 containing the variable. It is possible for the instance of
Class1
  to be adjacent in memory to the variable just read by Thread 1, and to
  be loaded into the cache on CPU 1 as well, because a single cache line
  can hold more than one variable.

- Thread 2 constructs the Class1 instance and initializes the _val member.

- Thread 1 calls getVal() on the instance. Because the corresponding
  memory location is in the cache, it is served from the cache, and
  thread 1 reads whatever garbage is in that memory location.

Without a memory barrier, there is no way for Thread 1 to know that
Thread 2 has modified the contents of the memory that holds _val.

So, I guess another way to phrase my question is to ask whether
C# guarantees to insert a memory barrier at the end of the
constructor if readonly members were initialized by that constructor.

> Moving on to Mono, one major problem is that the CLI standard, as
> currently specified, uses effectively the same memory consistency model
> as Java.  Meaning, C++ techniques such as double-checked locking ARE NOT
> VALID:

Aha. OK -- that answers my question, thanks!

Could you point me at where the CLI memory consistency model is defined?
I couldn't find any such thing in the C# documentation.

>
> private static Class1 foo;
>
> public static Foo {
> get {
> // This will likely work on most platforms, such
> // as x86, but it is NOT guaranteed to work on
> // all potential hardware platforms.
> if (foo == null) {
> lock (typeof(Something)) {
> if (foo == null)
> foo = new Class1();
> }
> }
> }
> }
>
> In C++, you could use code similar to the above, and you WOULD NOT need
> to lock both the class constructor and the accessor methods, as the
> calling code ensures that the class has properly constructed before
> invoking any member functions.
>
> The problem is that double-checked locking isn't really portable in
> .NET, so you either need to (a) always lock the code that will construct
> the object, or (b) use the static loader lock, described below.
>
> > What if the member variable is not readonly (but will
> > never be modified, except for the initial assignment
> > in the constructor)? Is accessing the value thread-safe
> > without a lock in that case?
>
> I would expect that this is similar/identical to the readonly variable
> case.  "readonly" only means that the compiler will check your code to
> ensure that the variables don't change after you've initialized them in
> the constructor.  The runtime may do some checking on them, but I'm not
> entirely sure about that.
>
> > What about const members? Is access to those safe without
> > a lock?
>
> Const members are safe, as these are "burned" into the CIL, and cannot
> be changed without recompiling.  They're just like enumeration members.
> You can't change enumeration values after you've compiled. :-)

Hmmm... Even those wouldn't be necessarily safe. The would be
safe only if the language definition guarantees that const members
end up in the initialized data segment. However, an equally valid
implementation for const members would be to initialize them
at run time, during program startup. In that case, all bets would
be off. So, does the CLR or C# guarantee that const members
are in the initialized data segment?

> > And what about static members that are (conceptually)
> > immutable (only initialized in the constructor and
> > never assigned again)? Is the lock required there?
>
> No.  Static members are initialized by the class constructor (".cctor"),
> and the runtime has an internal lock to ensure that only one thread
> executes the class constructor.

Right. But without the reading thread grabbing the lock, there is
nothing to tell the hardware that the thread may be reading
inconsistent memory, I would think.

> // Initializing static members either "inline" or in the static
> // class constructor is *always* thread safe.
> public static readonly string Hello = "Hello";
> public static readonly Class1 Something;
> static MyClass ()
> {
> Something = new Class1 ();
> }

Hmmm. So if another thread reads Something, what ensures
that the second thread doesn't read from a stale cache line?

Cheers,

Michi.
--
Michi Henning              Ph: +61 4 1118-2700
ZeroC, Inc.                http://www.zeroc.com





More information about the Mono-devel-list mailing list