Automatic locking for C++ local static initialization

Christophe de Dinechin ddd at cup.hp.com
Wed Aug 2 17:48:27 UTC 2000


Jim,


I am sorry, I should have described the HP scheme more in details earlier... As
far as I can tell, we do not lock during the initialization of the object, we
lock only during the manipulation of a global list of objects to destroy. I
believe we don't have the problem Hans indicated.

The calling sequence for initializing a static object is something like:

	// Short circuit for the case where static is initialized
	if (!flag.initialized) {
		// Try to acquire the flag
		if (__checkLocalStatic(&flag)) {
			Ctor();
			__addLocalStatic(&flag);
		}
	}

If we get back to Hans' scenario, we have:

        Thread 1                Thread 2
        --------                --------
        call h
        acquire M
                                call f
				call checkLocalStatic
                                acquire L
				update f static state, "in progress"
				release L
				checkLocalStatic returns 1
                                call g
                                try to acquire M (wait, holding L)
        call f
	call checkLocalStatic
	acquire L
	wait while object state == "in progress"
	timeout: emit error "Recursive initialization".
	checkLocalStatic returns 1
	proceed to "second" initialization.


As Dennis pointed out, I believe this is a correct behavior. The "manual"
locking mechanism causes you to enter static initialization twice from two
different threads, and according to the standard, this is illegal.

Note that this also happens in the situation that Hans considers good, albeit
silently.

>         Thread 1                Thread 2
>         --------                --------
>         call h
>         acquire M
>                                 call f
>                                 call g
>                                 try to acquire M (wait)
>         call f

Here, we are starting to initialize the same object twice. In general, this is
not good.

>         call g
>         acquire M recursively
>         finish initialization
>         exit g, f
>         release M
>         exit h                  acquire M
>                                 initialization is done
>                                 release M
>                                 exit g, f
>

Of course, this is a matter of philosophy, but I believe aCC just identify a
runtime mistake you made. Note that there is a simpler way to run into a similar
problem:

	struct X {
		X(int i) { static X x(1); }
	};

This is considered valid by several compilers. aCC issues an error at runtime,
"recursive initialization of local static".


Best regards
Christophe

Jim Dehnert wrote:
> 
> As was suggested at the last meeting, I contacted Hans Boehm about
> initialization locking to try to clarify why he was opposed to
> automatic locking.  We (mostly Hans) worked up this example, which
> illustrates one point, that it can produce undeserved deadlocks.
> 
> Suppose we have a program written for a pthreads environment that
> supports recursive locks (common these days, but not necessary if one
> goes to a bit more trouble).  It contains a data cluster
> (i.e. a number of interconnected components) that the
> programmer wishes to initialize the first time it is referenced,
> and the first reference may come through a number of different paths.
> One of those paths enters through function f, which defines some static
> data, one of which is constructed using function g, which is part of
> the cluster; others enter elsewhere and eventually call f.
> 
> So the conscientious programmer sets up a mutex lock M, and acquires it
> at each potential entry to the cluster before proceeding, which
> includes g and some other function h.  Now consider the following
> execution sequence:
> 
>         Thread 1                Thread 2
>         --------                --------
>         call h
>         acquire M
>                                 call f
>                                 call g
>                                 try to acquire M (wait)
>         call f
>         call g
>         acquire M recursively
>         finish initialization
>         exit g, f
>         release M
>         exit h                  acquire M
>                                 initialization is done
>                                 release M
>                                 exit g, f
> 
> Everything has worked exactly as the programmer intended.
> 
> But now we add automatic locking for the static initialization in F,
> say using lock L.  What happens?
> 
>         Thread 1                Thread 2
>         --------                --------
>         call h
>         acquire M
>                                 call f
>                                 acquire L
>                                 call g
>                                 try to acquire M (wait, holding L)
>         call f
>         try to acquire L (wait, holding M)
> 
>                 DEADLOCK
> 
> So I think what we have here is reasonably designed initialization
> scenario (at least if you believe in recursive locking) that works as
> written, but fails when we insert automatic static initialization
> locks.
> 
> Other reasons raised earlier, and appearing in the G-4 commentary, are:
> 
>  1) There are more efficient methods available to a programmer than
>     per-object locks, and doing the latter automatically makes it hard
>     to optimize this.
> 
>  2) Mike Ball objects that interaction with exceptions can't be
>     Standard-conforming with automatic locks.
> 
>  3) Hans believes that there are complications with certain legal
>     optimizations of the function-scope object initialization.
> 
> It would be worth people re-familiarizing themselves with the
> commentary (in the Open Issues document) before the meeting.
> 
> Jim
> 
> -               Jim Dehnert  x3-4272




More information about the cxx-abi-dev mailing list