【正文】
?baked in? to the infrastructure, there is something of a mismatch between the 1970s model of concurrency on a single machine (shared memory, threads, synchronization based on mutual exclusion) and the asynchronous, message based style that one uses for programming web based applications and services. C therefore seems an ideal test bed for our ideas on language support for concurrency in mainstream languages. 2. POLYPHONIC C LANGUAGE OVERVIEW This section describes the syntax and semantics of the new constr ucts in Poly phonic C and then gives a more precise, though still informal, speci?cation of the syntax. The Basic Idea To C ?s fairly conventional object oriented programming model, Polyphonic C adds just two new concepts: asynchronous methods and chords. Asynchronous Methods. Conventional methods are synchronous, in the sense that the caller makes no progress until the callee pletes. In Polyphonic C , if a method is declared asynchronous then any call to it is guaranteed to plete essentially immediately. Asynchronous methods never return a result (or throw an exception)。?. The body is only executed once all the methods in the header have been called. Method calls are implicitly queued up until/unless there is a matching chord. Consider for example public class Buffer { public string Get() amp。 } } The code above de?nes a class Buffer with two instance methods, which are jointly de?ned in a 14 single chord. Method string Get() is a synchronous method taking no arguments and returning a string. Method async Put(string s) is asynchronous (so returns no result) and takes a string argument. If buff is a instance of Buffer and one calls the synchronous method buff . Get() then there are two possibilities: — If there has previously been an unmatched call to buff . Put(s) (for some string s) then there is now a match, so the pending Put(s) is dequeued and the body of the chord runs, returning s to the caller of buff . Get(). — If there are no previous unmatched calls to buff . Put(.) then the call to buff . Get() blocks until another thread supplies a matching Put(.). Conversely, on a call to the asynchronous method buff . Put(s), the caller never waits, but there are two possible behaviours with regard to other threads: — If there has previously been an unmatched call to buff . Get() then there is now a match, so the pending call is dequeued and its associated blocked thread is awakened to run the body of the chord, which returns s. — If there are no pending calls to buff . Get() then the call to buff . Put(s) is simply queued up until one arrives. Exactly which pairs of calls are matched up is unspeci?ed, so even a single threaded program such as Buffer buff = new Buffer()。 buff . Put(“sky”)。 is nondeterministic (printing either “bluesky” or “skyblue”).3 Note that the implementation of Buffer does not involve spawning any threads: whenever the body of the chord runs, it does so in a preexisting thread (viz. the one that called Get()). The reader may at this point wonder what the rules are for deciding in which thread a body runs, or how we know to which method call the ?nal value puted by the body will be returned. The answer is that in any given chord, at most one method may be synchronous. If there is such a method, then the body runs in the thread associated with a call to that method, and the value is returned to that call. Only if there is no such method 3In a real implementation the nondeterminism in this very simple example may be resolved statically, so different executions will always produce the same result。 public async Put(string s) { return s。 public async Put(int n) { 15 return ()。 methodheader]? body Methodheader : := attributes modi?ers returntype membername(formals). We call a chord declaration trivial if it declares a single, synchronous method (. it is a standard C method declaration). WellFormedness Extended classes are subject to a number of well formedness conditions: — Within a single methodheader: (1) If returntype is async then the formal parameter list formals may not contain any ref or out parameter — Within a single chorddeclaration: (2) At most one methodheader may have a nonasync returntype. (3) If the chord has a methodheader with returntype type, then body may use return statements with type expressions, otherwise body may use empty return statements. (4) All the formals appearing in methodheaders must have distinct identi?ers. (5) Two methodheaders may not have both the same member name and the same argument type signature. (6) The methodheaders must either all declare instance methods or all declare static methods. — Within a particular class: (7) All methodheaders with the same membername and argument type signature must have the same returntype and identical sets of attributes and modi?ers. (8) If it is a value class (struct), then only static methods may appear in nontrivial chords. (9) If any chorddeclaration includes a virtual method m with the override modi?er,5 then any method n that appears in a chord with m in the super class containing the overridden de?nition of m must also be overridden in the subclass. Most of these conditions are fairly straightforward, though Conditions 2 and 9 deserve some further ment. Condition 9 provides a conservative, but simple, sanity check when re?ning a 16 class that contains chords since, in general, implementation inheritance and concurrency do not mix well [Matsuoka and Yonezawa 1993] (see Four et al. [2020] for a discussion of “inheritance anomalies” in the context of the join calculus). Our approach here is to enforce a separation of these two concerns: a series of chords must be syntactically local to a class or a subclass declaration。 virtual async g () { /? body1 ?/ } virtual void f ()