This is the mail archive of the gcc@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Optimization of conditional access to globals: thread-unsafe?


Samuel Tardieu wrote:
On 26/10, Robert Dewar wrote:

| Of course in Ada there is a clear notion of threads semantic, and
| a clear definition of what the meaning of code is in the presence
| of threads, so the specific situation discussed here is easy to
| deal with (though Ada takes the view that unsychronized shared access to
| non-atomic or non-volatile data from separate threads has undefined
| effects).

In the following example, is the access to "Shared" considered
unsynchronized even though what looks like a proper lock is used
around it?

Yes, it is unsynchronized. Why would you think otherwise? (referencing
the RM). You can't adopt a naive memory model in Ada! One way to think
about things is that tasks are free to keep local copies of all global
variables, synchronizing their values only at a point of synchronization. Locking of this kind needs to be done with
entry barriers.

package P is


Shared : Natural := 0;

procedure Maybe_Increment;

end P;


package body P is


   protected Lock is
      procedure Maybe_Lock (Locked : out Boolean);
      procedure Always_Unlock;
   private
      Is_Locked : Boolean := False;
   end Lock;

protected body Lock is

      procedure Always_Unlock is
      begin
         Is_Locked := False;
      end Always_Unlock;

      procedure Maybe_Lock (Locked : out Boolean) is
      begin
         Locked    := not Is_Locked;
	 Is_Locked := True;
      end Maybe_Lock;

end Lock;

   procedure Maybe_Increment is
      L : Boolean;
   begin
      Lock.Maybe_Lock (L);
      if L then
         Shared := Shared + 1;
      end if;
      Lock.Always_Unlock;
   end Maybe_Increment;

end P;

By naively reading the code, I would assume that if two tasks were to
call Maybe_Increment once, after completion of those tasks Shared would
contain either 1 or 2, depending on whether they both got the lock in
turn or if only one of them got it.

However, if you look at the x86 code for Maybe_Increment (-O3
-fomit-frame-pointer -fno-inline), you'll see:

 1   p__maybe_increment:
 2   .LFB11:
 3         subl    $12, %esp
 4 .LCFI6:
 5         movl    $p__lock, %eax
 6         call    p__lock__maybe_lockP
 7         cmpb    $1, %al
 8         movl    p__shared, %eax                 <=== unconditional load
 9         sbbl    $-1, %eax                       <=== conditional +1
10         movl    %eax, p__shared                 <=== unconditional store
11         movl    $p__lock, %eax
12         addl    $12, %esp
13         jmp     p__lock__always_unlockP

Note lines 8 to 10: on a multiprocessor system with both tasks running at
the same time on different processors, you can end up with Shared being
zero after the two tasks have ended (for example if the task getting the
lock runs one or two instructions ahead the one without the lock).

Sam



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]