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

Jonathan Pryor jonpryor at vt.edu
Wed Feb 18 18:36:14 EST 2004


Below...

On Wed, 2004-02-18 at 14:24, Michi Henning wrote:
> Jonathan Pyor wrote:
<snip/>
> 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.

In C++, here's the main question: where is Class1 allocated, and what
type is it?  There are only three potential answers:

  1.  Globally: class instance (Class1 global): constructor
      will be initialized before main() is executed.  Unless you're
      creating threads from within the constructor (are you insane?!),
      you don't need to worry about running within a multi-threaded
      environment.

      Here, your scenario isn't possible, as only one thread is present.

  2.  On the stack: Unless threads share stack space (!), this isn't
      a problem.

  3.  On the Heap: Here, your scenario is present.  However, in order to
      share the class instance between threads, the pointer needs to be
      globally allocated, in which case it will be default-initialized 
      to the null pointer.  So in your scenario, when Thread 1 reads in
      the memory block containing class instance, it reads NULL, 
      executes the member function, and your program DIES.

      The only time this wouldn't occur is when you're allocating a new
      instance over an old instance:

          Class1* p = new Class1 ();
          // ...
          p = new Class1 ();

      In this case, you better have an intervening delete, or you have 
      a memory leak, and you should set the pointer to NULL after the
      delete (to avoid double-deletions), reducing this scenario to the
      first.

      Translation: Properly coded, you WILL NOT have one thread access
      a class instance before it's been fully constructed, UNLESS your
      architecture doesn't have atomic writes for pointers.

Do you need a write barrier?  Yes.  The easiest way to do this is to
just mark the pointer as "volatile", requiring that the compiler always
re-check the memory on reads.

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

No.  See other posts about the lax memory model semantics provided by
the CLI standard.

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

You don't want the C# standard, you want the CLI standard, ECMA 335:

	http://www.ecma-international.org/publications/standards/Ecma-335.htm

In my printed version, you want Partition I, section 11.6 (Memory Model
and Optimizations).

<snip/>

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

Const members are *so* const, that the compiler is permitted to inline
their values so that cross-assembly references are removed.  So:

	enum Foo {Bar = 42;}
	Foo foo = Foo.Bar;

is basically the same as

	int foo = 42;

With the added provision that some static type checking is present, but
as far as the runtime is concerned enumerations are silently convertible
to the underlying integer type.

Of course, that doesn't hold for System.String instances, but you can
safely assume that all constants will be initialized in a thread-safe
manner.  The runtime ensures this.  (If it doesn't, it's a runtime bug.)

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

The runtime runs the class constructors from within a global runtime
lock.  No other threads can access any class members (fields, methods,
constructors, etc.) until the class constructor finishes executing.  And
if the class constructor generates an exception, you get a
TypeLoadException, and you'll NEVER be able to access *anything* on the
class.

> > // 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?

The runtime ensures that this can't happen.  How that's done is
implementation dependent. :-)

 - Jon





More information about the Mono-devel-list mailing list