Possible language changes

jimmaureenrogers at att.net jimmaureenrogers at att.net
Tue Mar 1 04:26:25 GMT 2005


In the interest of full disclosure I want to make it clear that my understanding of concurrency
issues arises from my study and uses of both Java and Ada 95.

In my opinion the Java model has many weaknesses that complicate the use of concurrency.
The most significant weakness is synchronization of function-scoped statics. The model allowing
any thread to call a public function in any other thread, and thereby complicate the use of
function-scoped static variables, makes efficient reuse of classes problematic.

Simply adding synchronization to protect against unexpected synchronization incurs an
unwaranted overhead for non-threaded uses. Failing to add synchronization makes a
function not thread-safe.

C++ needs to maintain its features supporting efficiency and preventing the imposition
of unwanted overhead.

I much prefer the Ada protected object model for inter-task communication.
Ada protected objects are passive communication objects that are protected from
inappropriate concurrent access. They are always thread-safe. Protected objects
are used as communication glue between active objects (tasks). Each Ada task
runs as a separate thread. There is no way for one task to call the subprograms
of another task. This isolation avoids the problems of the Java model. Task
communication is accomplished through calls to a protected object, which is always
executed in the calling task.

Protected objects provide three kinds of synchronization for tasks. Each kind of
synchronization is implicitly provided by one of the three kinds of methods available
to protected objects. Those three kinds of methods are procedures, entries, and
functions.

Tasks calling a protected procedure are granted exclusive, unconditional access to
the protected object. Protected procedures are expected to modify the state of the
protected object. Protected entry boundary conditions are automatically
re-evaluated at the end of each protected procedure.

Tasks calling a protected entry are granted exclusive, conditional access to the
protected object. All calls to a protected entry execute only when the boundary
condition evaluates to TRUE. Calls are processed serially, so that the boundary
condition may be evaluated after each call completes. Tasks that are waiting for
the boundary condition to be TRUE, or are waiting for their turn to execute
a protected entry, are placed in an automatically created and managed entry queue.
Each entry for a protected object has its own queue.

Tasks calling a protected function are granted shared access to the protected object.
Protected functions use a read/write lock. Protected functions are not supposed to
change the state of the protected object. They are intended to be read-only 
operations. Since they are read-only, there is no race condition involved in allowing
several tasks to execute the same or different protected functions at the same time
on the same protected object.

The following example of a bounded buffer implemented as a protected type 
illustrates the use of all three kinds of protected methods in an Ada protected
object.

   type Node;

   type Node_Access is access Node;

   type Node is record
      Value : Element_Type;
      Next  : Node_Access := null;
   end record;

   protected type Unbounded_Buffer is
      procedure Put(Item : in Element_Type);
      entry Get(Item : out Element_Type);
      function Size return Natural;
   private
      Head : Node_Access := null;
      Tail : Node_Access := null;
      Count : Natural := 0;
   end Unbounded_Buffer;


   protected body Unbounded_Buffer is
      procedure Put(Item : in Element_Type) is
         Temp_Node : Node_Access := new Node;
      begin
         Temp_Node.Value := Item;
         if Tail = null then
            Head := Temp_Node;
            Tail := Temp_Node;
         else
            Tail.Next := Temp_Node;
            Tail := Tail.Next;
         end if;
         Count := Count + 1;
      end Put;

      entry Get(Item : out Element_Type) when Head /= null is
         procedure Free is new 
            Ada.Unchecked_Deallocation(Object => Node, Name => Node_Access);
         Temp : Node_Access;
      begin
         Item := Head.Value;
         Temp := Head;
         Head := Head.Next;
         if Head = null then
            Tail := null;
         end if;
         Free(Temp);
         Count := Count - 1;
      end Get;

      function Size return Natural is
      begin
         return Count;
      end Size;
   end Unbounded_Buffer;

Although it may appear that the protected type Unbounded_Buffer is
declared twice, what is really happening is a separation of interface
definition and implementation. The first part is the interface
definition of the protected type Unbounded_Buffer. The second part 
is the implementation. I am sure you notice that none of the locking
or queuing is explicit. Only the buffer operations are explicit.

The Java model does not provide a read/write lock.
It does not, strictly speaking, provide a conditional lock.
The Java threading model is non-deterministic and only works well
on the average.

--
Jim Rogers 
Colorado Springs, Colorado 
U.S.A.


-------------- Original message from "Boehm, Hans" <hans.boehm at hp.com>: -------------- 


> Ben - 
> 
> I think this is useful, but we might be getting ahead of ourselves 
> a bit. 
> 
> Should we try to first identify the important issues and then try 
> to get a preliminary consensus to run past the C++ committee? I think 
> your list is helpful in identifying the issues. 
> 
> Based on an earlier message from Herb Sutter, it sounds like it would 
> be useful to trey to have someone at the Lillehammer meeting and get 
> some feedback on some of these issues. Is someone planning on being 
> there 
> anyway? Does anyone know what the deadline is for getting something 
> into the mailing for that meeting? 
> 
> On the memory model side, I think the major identified issue is 
> 
> 1) Do we leave the semantics of programs containing races undefined? 
> The only people who have commented on this are somewhat in favor, I 
> think. 
> 
> Other known issues: 
> 
> 2) Can we restrict the compiler introduction of visible writes to 
> only the bit-field case? 
> I would argue "yes". Otherwise we need an alternate, weaker 
> restriction. 
> Note that the way I've phrased this, it disallows speculative register 
> promotion, which is now done by many compilers. I know of no way to 
> avoid that. 
> 
> 3) Should we allow the compiler to reload local variable from globals, 
> as some compiler do now. Assuming the answer to (1) is yes, I think the 
> answer here is implicitly "yes". 
> 
> 4) Should volatile reads/writes count as synchronization operations and 
> have acquire/release semantics as in Java? I would argue yes. But this 
> is a significant change on many platforms (though not Itanium), and is 
> controversial. 
> 
> 5) Should initialization of function-scoped statics be implicitly 
> synchronized? 
> I think we need to figure out what implementations currently do. My 
> personal opinion is probably no, but I already lost this argument once 
> when the Itanium ABI was designed. (Argument pro: The shared variable 
> is compiler-introduced, and hence it would be very hard for the 
> programmer 
> to explicitly synchronize. Argument con: It introduces potentially 
> expensive synchronization where it is likely to never be needed, 
> and they're hidden from the programmer's view. It 
> requires the compiler to generate implicit calls into the thread 
> runtime. 
> Drastic alternative: Deprecate the construct in the hard cases.) 
> Opinions? 
> 
> 6) I'm sure there are issues with exceptions, but I haven't looked 
> deeply. Anybody want to point out what they are? 
> 
> Anything else? 
> 
> Hans 
> 
> > -----Original Message----- 
> > From: Ben Hutchings [mailto:ben at decadentplace.org.uk] 
> > Sent: Sunday, February 27, 2005 5:28 AM 
> > To: Andrei Alexandrescu; Boehm, Hans; Kevlin Henney; Bill 
> > Pugh; Douglas C. Schmidt; Doug Lea; Jim Rogers; Maged 
> > Michael; asharji at plg.uwaterloo.ca; Richard Bilson 
> > Subject: Possible language changes 
> > 
> > 
> > Here's a list I came up with some months ago, but only posted 
> > to the mailing list that I hastily set up and which we 
> > haven't used. I think I'm going to have a go at drafting an 
> > actual proposal soon, but I don't know whether I'm really up 
> > to that task. 
> > 
> > I skimmed through the language part of the standard, looking 
> > for anything that might be affected by concurrency. 
> > 
> > Most importantly: 
> > 
> > 1.3 [intro.defs] We may need to add definitions of "thread" 
> > and other terms. 
> > 
> > 1.9/6-11 [intro.execution] We need to change the 
> > specification of the sequencing of operations and the 
> > definition of observable behaviour to allow for concurrency 
> > and synchronisation. We may wish to specify more precise 
> > semantics for volatile. 
> > 
> > 3.6.1, 3.6.3 [basic.start.main, basic.start.term] We should 
> > either specify what happens if main exits or if exit() or 
> > abort() is called while multiple threads exist, or else state 
> > that this results in undefined behaviour. 
> > 
> > 3.6.2/3 [basic.start.init] We should specify that static 
> > initialisation at namespace scope is always synchronised if 
> > done while multiple threads exist. 
> > 
> > 6.7/4 [stmt.dcl] We should specify static initialisation at 
> > local scope in such a way that it can be or is always synchronised. 
> > 
> > 15 [except] We must change some parts of this slightly to 
> > allow for concurrency, including at least 15.1/7, 15.1/8, 
> > 15.2/1, 15.2/2, 15.5.3 
> > 
> > Plus optionally: 
> > 
> > 2.11 [lex.key] We may wish to add keywords. 
> > 
> > 6 [stmt.stmt] We may wish to add synchronisation statements. 
> > 
> > 7 [dcl.dcl] We may wish to add some new form of declaration. 
> > 
> > -- 
> > Ben Hutchings 
> > Life would be so much easier if we could look at the source code. 
> > 






More information about the cpp-threads mailing list