[cpp-threads] Prism 0.9.1, and further on the effects of races
Peter Dimov
pdimov at mmltd.net
Fri Sep 22 02:49:19 BST 2006
Herb Sutter wrote:
> (Sorry for the lag, I'm on the road and email is intermittent.)
>
> Peter wrote:
>> Herb Sutter wrote:
>>>
>>> thread 1: global_ptr = new Derived();
>>>
>>> With no constraints, this could execute as:
>>>
>>> global_ptr = (Derived*)operator new( sizeof(Derived) );
>>> global_ptr->vptr = &Base::vtable; // a
>>> global_ptr->Base ctor body;
>>> global_ptr->vptr = &Derived::vtable; // b
>>> global_ptr->Derived ctor body;
>>>
>>> And in a race we perform:
>>>
>>> thread 2: global_ptr->DerivedFunc(); // c
>>
>> This isn't a problem in either model A (undefined behavior) and
>> model B (global_ptr has an undefined value in the event of a race).
>> In both cases, the (c) statement is allowed to trap.
>>
>> It is only a problem if a read that participates in a race is
>> constrained to
>> only return one of the possible values that the variable could hold
>> if the race was resolved in a sequentially consistent manner, but
>> this is generally
>> not true for non-atomic variables (if it were, we wouldn't need raw
>> atomics.)
>
> Good points. For a moment, let's restrict to the case where
> global_ptr is a type that is known to be atomically read/written --
> either a proposed std::atomic<Derived*> or a plain Derived* on a
> platform that happens to document a guarantee that reads and writes
> from a plain default-aligned T* are atomic.
>
> In line c, the behaviors that I think will be unsurprising to users
> are: (1) a correct function call on a completely constructed object,
> and (2) a null pointer trap.
>
> The question is, should we also permit general undefined behavior,
> such as a wild branch?
If the store to global_ptr does not have release semantics, the behavior is
undefined since thread 2 is not required to observe the updates to
*global_ptr because they can become visible after the update to global_ptr.
If the store to global_ptr does have release semantics, on all hardware
except Alpha thread 2 will observe either NULL or a fully constructed object
because of the data dependency between global_ptr and *global_ptr. However,
if Derived::Derived updates a global variable as a side effect, this update
might not become visible to thread 2, unless the read from global_ptr has
acquire semantics.
I think.
> Here's a different and maybe more useful way to put this question:
> Should line c be allowed any additional behaviors than if global_ptr
> pointed to a type having no virtual functions?
I don't see why there should be any difference. The vptr is just a hidden
member of the object. Consider:
struct Derived
{
int * p;
int q;
Derived(): p( &q ), q( 5 ) {}
void DerivedFunc() { std::cout << *p << std::endl; }
};
If the store to global_ptr doesn't have release semantics,
global_ptr->DerivedFunc() can easily access an uninitialized p and trap.
More information about the cpp-threads
mailing list