Re: Multi-threaded C++ programs: wrought with peril?



Joe wrote:
Marc Sherman wrote:
As long as the thread synchronization API's are used (eg. WaitForXXX,
InterLockXXX, etc.), why would faith be needed?


Technically, those apis don't mean anything to C++, so the compiler is
free to reorder as it wills. That is if we have (I don't feel like
looking up the exact apis so I will use lock() unlock() and increment()
):


for (int ix = 0; ix < 10; ++ix)
{
lock();
increment(count);
unlock();
}


the compiler is free to generate code as if you had written:

increment(count, 10);

for (int ix=0; ix < 10; ++ix)
{
lock();
unlock();
}

Another example (which doesn't require the optimizer to reorder OS API calls):

for (int ix = 0; ix < 10; ++ix)
{
lock();
++count;
unlock();
}

could be modified to:

count += 10;
for (int ix = 0; ix < 10; ++ix)
{
lock();
unlock();
}

if the compiler could prove that lock and unlock don't access count. The faith is really about the fact that you have to "assume" that the compiler isn't smart/stupid enough to see that lock and unlock don't affect count (which they don't directly, of course), and thus reorder things.

You can see how that might mess up a multithreaded program.
Fortunately, the microsoft compiler won't do hoisting when locks are
involved (I don't think anyway), so it isn't a problem, but there is
nothing in the C++ standard guaranteeing that. It's really the same
thing with the double checked locking algorithm. Without some
guarantees from the compiler (or a sufficiently non-optimizing
compiler), one of the checks can be removed by the optimizer and things
break.

The faith comes in that the compiler won't do optimizations when
locking apis are in use. One problem is that it can be hard to
determine if locking apis are being used. If you wrap the apis in a
class, then it can be hard for the compiler to see them. I suppose
most use inline code for their wrappers, but it's conceivable that
someone is using a seperately compiled class as a wrapper. In that
case, the compiler may not know that it shouldn't hoist code out of
loops.

Fortunately (for multithreaded code), the compiler is generally unable to do any hoisting of potentially thread shared variables (e.g. globals and potentially aliased variables) at all through OS API calls, due to the fact it can't see into the API calls and therefore doesn't know whether they might access those variables, or whether the API calls contain any observable behaviour. For the same reason, it usually can't reorder OS API calls. e.g.

extern int i;
i = 10;
api1();
i = 11;
api2();
print(i);

Since the compiler doesn't know whether api1 or api2 might access i, it can't optimize any of the writes to i. At least, I have "faith" that this is the case.

Two more examples:

int i = 10; //i is a local variable
api1();
i = 11;
api2();
print(i);

Here, the compiler can change it to:
api1();
api2();
print(11);

since it is clearly impossible for api1 and 2 to access i.

Finally:
int i = 10;
api1(&i);
i = 11;
api2();
print(i);

Here, no i optimization is possible again, due to the fact that i may be aliased.

Tom
.



Relevant Pages

  • Re: Multi-threaded C++ programs: wrought with peril?
    ... InterLockXXX, etc.), why would faith be needed? ... Technically, those apis don't mean anything to C++, so the compiler is ... The compiler is not allowed to reorder sequence points, ...
    (microsoft.public.win32.programmer.kernel)
  • Re: object system...
    ... [A C compiler cannot put a loop control variable in a register without ... and then later may assemble them into ... this whole API may be modified and redirected (in ... functionality it exports, but instead redirects it to the particular library ...
    (comp.object)
  • Re: Quick question about streams...?
    ... For loops and structures are the C language. ... The API is a bunch of functions. ... I don't know what "runs on assembler" means. ... A C compiler usually starts ...
    (microsoft.public.vc.language)
  • Re: Double-Checked Locking pattern issue
    ... The risk we do not know such reorder is, we will write wrong code -- even very common code like Singleton pattern -- in some specific environment, like this, compiler will reorder the instructions to assign the return instance pointer value before real construct. ... It all depends on what other code is in the function, what parts of the CPU are being used. ... All that the programmer can rely on is that the CPU may, for its own reasons, reorder memory reads and writes between memory barriers, as long as each result is the same, considering only the single thread. ...
    (microsoft.public.vc.language)
  • Re: Evening
    ... you wrote your own compiler!? ... Faith in bob knows what. ... You had to be root, or equivalent, any sane distro only gives you write ... priveledges to your home directory and tmp. ...
    (alt.2600)