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

Michi Henning michi at zeroc.com
Wed Feb 18 20:24:07 EST 2004


Jonathan Pryor wrote:

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

I think that is correct (assuming no threads are created in a global
constructor.

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

Hmmm... It's not uncommon for one thread to hold a pointer to a stack-
allocated variable in another thread, so I guess the problem could
arise in that case.

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

I don't understand that one. Consider:

Thread 1 (some time after main() was entered):

    Class1 *p = new Class1;

    // Pass p to Thread 2 or, alteratively, assign p to some
    // global variable, properly protected by a lock.

    lock(someMutex);
    global_p = p;
    unlock(someMutex);

Thread 2:

    // Use the passed p to call getVal() or, alternatively,
    // read the global variable containing p, again properly
    // protected by a lock, and then call getVal().

    lock(someMutex);
    Class1 *p = global_p;
    unlock(someMutex);
    // ...
    p->getVal();  // Problem here, I think

The problem here is that p is passed correctly, but no lock is
ever acquired that would protect the contents of the instance
pointed to by p, namely _val.

If Thread 2 previously has read a variable on the heap and
the cache line containing that variable also happens to contain
the instance of Class1, Thread 2 can read a stale value of _val
when it calls getVal(), no?

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

My concern isn't accessing the value before the instance is constructed.
The concern is reading a stale value of _val some time after the instance
is constructed. Without a lock in the constructor and getVal(), I don't
see any way the hardware could figure out when it might possibly reading
stale memory.

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

I've never been comfortable with the semantics of volatile. I agree,
things should work correctly if something is marked as volatile,
but I'm not sure the C++ standard actually guarantees that a memory
barrier will be placed around a volatile access by the compiler.
I think all that volatile guarantees is that memory will be read
on access, so things will work correctly, for example, for memory-
mapped registers. But I'm not sure that volatile guarantees memory
consistency.

And, of course, in C#, there is no volatile keyword, so I guess
I still have to use a hard lock.

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

OK, thanks, that is the main thing I was concerned about.


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

Thanks muchly!

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

Hmmm... Doesn't the language spec say that things will be initialized
some time before they are accessed, but not necessarily on program
startup? If the compiler generates code to do lazy initialization, I
think things could still go wrong?

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

Sure, but I'm not concerned about accessing the instance before it is
constructed, but about accessing the contents of the instance after
construction without holding a lock. In that case, I don't think the
hardware can know that I might possibly be reading stale memory.

Cheers,

Michi.




More information about the Mono-devel-list mailing list