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

Jonathan Pryor jonpryor at vt.edu
Wed Feb 18 06:49:35 EST 2004


Below...

On Tue, 2004-02-17 at 20:36, Michi Henning wrote:
> I have a question regarding thread safety...
> 
> Consider:
> 
> class Class1
> {
>      Class1()
>      {
>          _val = 42;
>      }
> 
>      public int getVal()
>      {
>          return _val;
>      }
> 
>      private readonly int _val;
> }
> 
> In a threaded environment, is it necessary to interlock
> inside the constructor and getVal() to ensure that
> threads get the correct value?
<snip/>
> In C++, the lock is necessary because, on SMP
> machines, memory consistency isn't guaranteed without
> the lock. But I don't know whether the same is true
> for C#.

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?

In C++, if your object is globally allocated, locking isn't necessary
(see the description about static members for an explanation).  If your
object is heap allocated, then you need locks around all your callee
code, NOT within your class, just to ensure that the pointer is non-null
(avoiding crashes):

	// This won't work...
	Class1* pClass;
	void thread1 () {pClass = new Class1();}
	void thread2 () {pClass->getVal();}

So no, the lock is NOT necessary in C++.  It depends on a number of
factors.  The lock is only necessary if you had multiple member
functions (NOT including the constructor) that could change the
underlying member variable.  If you allow your code to invoke both a
constructor and a member function at the same time, YOUR CODE IS BROKEN.

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:

	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. :-)

> 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.

	// 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 ();
	}

You can think of them as C++ global variables, which are initialized by
the C++ runtime libraries *before* main() is called, so you can assume
that there will only be a single thread initializing the class.  On the
other hand, the is no guarantee about order of initialization *between*
global variables in different compilation units, so there are still
problems with global variables in C++.

> Any difference in memory consistency models between
> Mono and .NET? (I need to write code that is portable
> to both platforms.)

If there are, file them in bugzilla. :-)

 - Jon





More information about the Mono-devel-list mailing list