[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