- File:
-
- 1 edited
-
doc/papers/concurrency/Paper.tex (modified) (32 diffs)
Legend:
- Unmodified
- Added
- Removed
-
doc/papers/concurrency/Paper.tex
r5453237 r08b5a7e 3 3 \articletype{RESEARCH ARTICLE}% 4 4 5 % Referees 6 % Doug Lea, dl@cs.oswego.edu, SUNY Oswego 7 % Herb Sutter, hsutter@microsoft.com, Microsoft Corp 8 % Gor Nishanov, gorn@microsoft.com, Microsoft Corp 9 % James Noble, kjx@ecs.vuw.ac.nz, Victoria University of Wellington, School of Engineering and Computer Science 10 11 \received{XXXXX} 12 \revised{XXXXX} 13 \accepted{XXXXX} 5 \received{26 April 2016} 6 \revised{6 June 2016} 7 \accepted{6 June 2016} 14 8 15 9 \raggedbottom … … 21 15 \usepackage{epic,eepic} 22 16 \usepackage{xspace} 23 \usepackage{enumitem}24 17 \usepackage{comment} 25 18 \usepackage{upquote} % switch curled `'" to straight … … 28 21 \renewcommand{\thesubfigure}{(\Alph{subfigure})} 29 22 \captionsetup{justification=raggedright,singlelinecheck=false} 30 \usepackage{dcolumn} % align decimal points in tables 31 \usepackage{capt-of} 32 \setlength{\multicolsep}{6.0pt plus 2.0pt minus 1.5pt} 23 \usepackage{siunitx} 24 \sisetup{binary-units=true} 33 25 34 26 \hypersetup{breaklinks=true} … … 40 32 \renewcommand{\linenumberfont}{\scriptsize\sffamily} 41 33 42 \renewcommand{\topfraction}{0.8} % float must be greater than X of the page before it is forced onto its own page43 \renewcommand{\bottomfraction}{0.8} % float must be greater than X of the page before it is forced onto its own page44 \renewcommand{\floatpagefraction}{0.8} % float must be greater than X of the page before it is forced onto its own page45 34 \renewcommand{\textfraction}{0.0} % the entire page maybe devoted to floats with no text on the page at all 46 35 … … 143 132 \makeatother 144 133 145 \newenvironment{cquote} 146 {\list{}{\lstset{resetmargins=true,aboveskip=0pt,belowskip=0pt}\topsep=3pt\parsep=0pt\leftmargin=\parindentlnth\rightmargin\leftmargin}% 147 \item\relax} 148 {\endlist} 149 150 %\newenvironment{cquote}{% 151 %\list{}{\lstset{resetmargins=true,aboveskip=0pt,belowskip=0pt}\topsep=3pt\parsep=0pt\leftmargin=\parindentlnth\rightmargin\leftmargin}% 152 %\item\relax% 153 %}{% 154 %\endlist% 155 %}% cquote 134 \newenvironment{cquote}{% 135 \list{}{\lstset{resetmargins=true,aboveskip=0pt,belowskip=0pt}\topsep=3pt\parsep=0pt\leftmargin=\parindentlnth\rightmargin\leftmargin}% 136 \item\relax 137 }{% 138 \endlist 139 }% cquote 156 140 157 141 % CFA programming language, based on ANSI C (with some gcc additions) … … 161 145 auto, _Bool, catch, catchResume, choose, _Complex, __complex, __complex__, __const, __const__, 162 146 coroutine, disable, dtype, enable, exception, __extension__, fallthrough, fallthru, finally, 163 __float80, float80, __float128, float128, forall, ftype, generator,_Generic, _Imaginary, __imag, __imag__,147 __float80, float80, __float128, float128, forall, ftype, _Generic, _Imaginary, __imag, __imag__, 164 148 inline, __inline, __inline__, __int128, int128, __label__, monitor, mutex, _Noreturn, one_t, or, 165 149 otype, restrict, __restrict, __restrict__, __signed, __signed__, _Static_assert, thread, 166 150 _Thread_local, throw, throwResume, timeout, trait, try, ttype, typeof, __typeof, __typeof__, 167 151 virtual, __volatile, __volatile__, waitfor, when, with, zero_t}, 168 moredirectives={defined,include_next}, 169 % replace/adjust listing characters that look bad in sanserif 170 literate={-}{\makebox[1ex][c]{\raisebox{0.4ex}{\rule{0.8ex}{0.1ex}}}}1 {^}{\raisebox{0.6ex}{$\scriptstyle\land\,$}}1 171 {~}{\raisebox{0.3ex}{$\scriptstyle\sim\,$}}1 % {`}{\ttfamily\upshape\hspace*{-0.1ex}`}1 172 {<}{\textrm{\textless}}1 {>}{\textrm{\textgreater}}1 173 {<-}{$\leftarrow$}2 {=>}{$\Rightarrow$}2 {->}{\makebox[1ex][c]{\raisebox{0.5ex}{\rule{0.8ex}{0.075ex}}}\kern-0.2ex{\textrm{\textgreater}}}2, 152 moredirectives={defined,include_next}% 174 153 } 175 154 … … 188 167 aboveskip=4pt, % spacing above/below code block 189 168 belowskip=3pt, 169 % replace/adjust listing characters that look bad in sanserif 170 literate={-}{\makebox[1ex][c]{\raisebox{0.4ex}{\rule{0.8ex}{0.1ex}}}}1 {^}{\raisebox{0.6ex}{$\scriptstyle\land\,$}}1 171 {~}{\raisebox{0.3ex}{$\scriptstyle\sim\,$}}1 % {`}{\ttfamily\upshape\hspace*{-0.1ex}`}1 172 {<}{\textrm{\textless}}1 {>}{\textrm{\textgreater}}1 173 {<-}{$\leftarrow$}2 {=>}{$\Rightarrow$}2 {->}{\makebox[1ex][c]{\raisebox{0.5ex}{\rule{0.8ex}{0.075ex}}}\kern-0.2ex{\textrm{\textgreater}}}2, 190 174 moredelim=**[is][\color{red}]{`}{`}, 191 175 }% lstset … … 213 197 } 214 198 215 % Go programming language: https://github.com/julienc91/listings-golang/blob/master/listings-golang.sty216 \lstdefinelanguage{Golang}{217 morekeywords=[1]{package,import,func,type,struct,return,defer,panic,recover,select,var,const,iota,},218 morekeywords=[2]{string,uint,uint8,uint16,uint32,uint64,int,int8,int16,int32,int64,219 bool,float32,float64,complex64,complex128,byte,rune,uintptr, error,interface},220 morekeywords=[3]{map,slice,make,new,nil,len,cap,copy,close,true,false,delete,append,real,imag,complex,chan,},221 morekeywords=[4]{for,break,continue,range,goto,switch,case,fallthrough,if,else,default,},222 morekeywords=[5]{Println,Printf,Error,},223 sensitive=true,224 morecomment=[l]{//},225 morecomment=[s]{/*}{*/},226 morestring=[b]',227 morestring=[b]",228 morestring=[s]{`}{`},229 % replace/adjust listing characters that look bad in sanserif230 literate={-}{\makebox[1ex][c]{\raisebox{0.4ex}{\rule{0.8ex}{0.1ex}}}}1 {^}{\raisebox{0.6ex}{$\scriptstyle\land\,$}}1231 {~}{\raisebox{0.3ex}{$\scriptstyle\sim\,$}}1 % {`}{\ttfamily\upshape\hspace*{-0.1ex}`}1232 {<}{\textrm{\textless}}1 {>}{\textrm{\textgreater}}1233 {<-}{\makebox[2ex][c]{\textrm{\textless}\raisebox{0.5ex}{\rule{0.8ex}{0.075ex}}}}2,234 }235 236 199 \lstnewenvironment{cfa}[1][] 237 200 {\lstset{#1}} … … 244 207 {} 245 208 \lstnewenvironment{Go}[1][] 246 {\lstset{language=Golang,moredelim=**[is][\protect\color{red}]{`}{`},#1}\lstset{#1}} 247 {} 248 \lstnewenvironment{python}[1][] 249 {\lstset{language=python,moredelim=**[is][\protect\color{red}]{`}{`},#1}\lstset{#1}} 209 {\lstset{#1}} 250 210 {} 251 211 … … 260 220 } 261 221 262 \newbox\myboxA 263 \newbox\myboxB 264 \newbox\myboxC 265 \newbox\myboxD 266 267 \title{\texorpdfstring{Advanced Control-flow and Concurrency in \protect\CFA}{Advanced Control-flow in Cforall}} 222 \title{\texorpdfstring{Concurrency in \protect\CFA}{Concurrency in Cforall}} 268 223 269 224 \author[1]{Thierry Delisle} … … 275 230 \corres{*Peter A. Buhr, Cheriton School of Computer Science, University of Waterloo, 200 University Avenue West, Waterloo, ON, N2L 3G1, Canada. \email{pabuhr{\char`\@}uwaterloo.ca}} 276 231 277 %\fundingInfo{Natural Sciences and Engineering Research Council of Canada}232 \fundingInfo{Natural Sciences and Engineering Research Council of Canada} 278 233 279 234 \abstract[Summary]{ 280 \CFA is a polymorphic, non-object-oriented, concurrent, backwards-compatible extension of the C programming language. 281 This paper discusses the design philosophy and implementation of its advanced control-flow and concurrent/parallel features, along with the supporting runtime written in \CFA. 282 These features are created from scratch as ISO C has only low-level and/or unimplemented concurrency, so C programmers continue to rely on library features like pthreads. 283 \CFA introduces modern language-level control-flow mechanisms, like generators, coroutines, user-level threading, and monitors for mutual exclusion and synchronization. 284 % Library extension for executors, futures, and actors are built on these basic mechanisms. 285 The runtime provides significant programmer simplification and safety by eliminating spurious wakeup and monitor barging. 286 The runtime also ensures multiple monitors can be safely acquired \emph{simultaneously} (deadlock free), and this feature is fully integrated with all monitor synchronization mechanisms. 287 All control-flow features integrate with the \CFA polymorphic type-system and exception handling, while respecting the expectations and style of C programmers. 288 Experimental results show comparable performance of the new features with similar mechanisms in other concurrent programming languages. 235 \CFA is a modern, polymorphic, \emph{non-object-oriented} extension of the C programming language. 236 This paper discusses the design of the concurrency and parallelism features in \CFA, and the concurrent runtime-system. 237 These features are created from scratch as ISO C lacks concurrency, relying largely on the pthreads library. 238 Coroutines and lightweight (user) threads are introduced into the language. 239 In addition, monitors are added as a high-level mechanism for mutual exclusion and synchronization. 240 A unique contribution is allowing multiple monitors to be safely acquired simultaneously. 241 All features respect the expectations of C programmers, while being fully integrate with the \CFA polymorphic type-system and other language features. 242 Finally, experimental results are presented to compare the performance of the new features with similar mechanisms in other concurrent programming-languages. 289 243 }% 290 244 291 \keywords{ generator, coroutine, concurrency, parallelism, thread, monitor, runtime, C, \CFA (Cforall)}245 \keywords{concurrency, parallelism, coroutines, threads, monitors, runtime, C, Cforall} 292 246 293 247 … … 300 254 \section{Introduction} 301 255 302 This paper discusses the design philosophy and implementation of advanced language-level control-flow and concurrent/parallel features in \CFA~\cite{Moss18,Cforall} and its runtime, which is written entirely in \CFA. 303 \CFA is a modern, polymorphic, non-object-oriented\footnote{ 304 \CFA has features often associated with object-oriented programming languages, such as constructors, destructors, virtuals and simple inheritance. 305 However, functions \emph{cannot} be nested in structures, so there is no lexical binding between a structure and set of functions (member/method) implemented by an implicit \lstinline@this@ (receiver) parameter.}, 306 backwards-compatible extension of the C programming language. 307 In many ways, \CFA is to C as Scala~\cite{Scala} is to Java, providing a \emph{research vehicle} for new typing and control-flow capabilities on top of a highly popular programming language allowing immediate dissemination. 308 Within the \CFA framework, new control-flow features are created from scratch because ISO \Celeven defines only a subset of the \CFA extensions, where the overlapping features are concurrency~\cite[\S~7.26]{C11}. 309 However, \Celeven concurrency is largely wrappers for a subset of the pthreads library~\cite{Butenhof97,Pthreads}, and \Celeven and pthreads concurrency is simple, based on thread fork/join in a function and mutex/condition locks, which is low-level and error-prone; 310 no high-level language concurrency features are defined. 311 Interestingly, almost a decade after publication of the \Celeven standard, neither gcc-8, clang-9 nor msvc-19 (most recent versions) support the \Celeven include @threads.h@, indicating little interest in the C11 concurrency approach (possibly because the effort to add concurrency to \CC). 312 Finally, while the \Celeven standard does not state a threading model, the historical association with pthreads suggests implementations would adopt kernel-level threading (1:1)~\cite{ThreadModel}. 313 314 In contrast, there has been a renewed interest during the past decade in user-level (M:N, green) threading in old and new programming languages. 315 As multi-core hardware became available in the 1980/90s, both user and kernel threading were examined. 316 Kernel threading was chosen, largely because of its simplicity and fit with the simpler operating systems and hardware architectures at the time, which gave it a performance advantage~\cite{Drepper03}. 317 Libraries like pthreads were developed for C, and the Solaris operating-system switched from user (JDK 1.1~\cite{JDK1.1}) to kernel threads. 318 As a result, languages like Java, Scala, Objective-C~\cite{obj-c-book}, \CCeleven~\cite{C11}, and C\#~\cite{Csharp} adopt the 1:1 kernel-threading model, with a variety of presentation mechanisms. 319 From 2000 onwards, languages like Go~\cite{Go}, Erlang~\cite{Erlang}, Haskell~\cite{Haskell}, D~\cite{D}, and \uC~\cite{uC++,uC++book} have championed the M:N user-threading model, and many user-threading libraries have appeared~\cite{Qthreads,MPC,Marcel}, including putting green threads back into Java~\cite{Quasar}. 320 The main argument for user-level threading is that it is lighter weight than kernel threading (locking and context switching do not cross the kernel boundary), so there is less restriction on programming styles that encourage large numbers of threads performing medium work units to facilitate load balancing by the runtime~\cite{Verch12}. 321 As well, user-threading facilitates a simpler concurrency approach using thread objects that leverage sequential patterns versus events with call-backs~\cite{Adya02,vonBehren03}. 322 Finally, performant user-threading implementations (both time and space) meet or exceed direct kernel-threading implementations, while achieving the programming advantages of high concurrency levels and safety. 323 324 A further effort over the past two decades is the development of language memory models to deal with the conflict between language features and compiler/hardware optimizations, \ie some language features are unsafe in the presence of aggressive sequential optimizations~\cite{Buhr95a,Boehm05}. 325 The consequence is that a language must provide sufficient tools to program around safety issues, as inline and library code is all sequential to the compiler. 326 One solution is low-level qualifiers and functions (\eg @volatile@ and atomics) allowing \emph{programmers} to explicitly write safe (race-free~\cite{Boehm12}) programs. 327 A safer solution is high-level language constructs so the \emph{compiler} knows the optimization boundaries, and hence, provides implicit safety. 328 This problem is best known with respect to concurrency, but applies to other complex control-flow, like exceptions\footnote{ 329 \CFA exception handling will be presented in a separate paper. 330 The key feature that dovetails with this paper is nonlocal exceptions allowing exceptions to be raised across stacks, with synchronous exceptions raised among coroutines and asynchronous exceptions raised among threads, similar to that in \uC~\cite[\S~5]{uC++} 331 } and coroutines. 332 Finally, language solutions allow matching constructs with language paradigm, \ie imperative and functional languages often have different presentations of the same concept to fit their programming model. 333 334 Finally, it is important for a language to provide safety over performance \emph{as the default}, allowing careful reduction of safety for performance when necessary. 335 Two concurrency violations of this philosophy are \emph{spurious wakeup} (random wakeup~\cite[\S~8]{Buhr05a}) and \emph{barging}\footnote{ 336 The notion of competitive succession instead of direct handoff, \ie a lock owner releases the lock and an arriving thread acquires it ahead of preexisting waiter threads. 337 } (signals-as-hints~\cite[\S~8]{Buhr05a}), where one is a consequence of the other, \ie once there is spurious wakeup, signals-as-hints follow. 338 However, spurious wakeup is \emph{not} a foundational concurrency property~\cite[\S~8]{Buhr05a}, it is a performance design choice. 339 Similarly, signals-as-hints are often a performance decision. 340 We argue removing spurious wakeup and signals-as-hints make concurrent programming significantly safer because it removes local non-determinism and matches with programmer expectation. 341 (Author experience teaching concurrency is that students are highly confused by these semantics.) 342 Clawing back performance, when local non-determinism is unimportant, should be an option not the default. 343 344 \begin{comment} 345 Most augmented traditional (Fortran 18~\cite{Fortran18}, Cobol 14~\cite{Cobol14}, Ada 12~\cite{Ada12}, Java 11~\cite{Java11}) and new languages (Go~\cite{Go}, Rust~\cite{Rust}, and D~\cite{D}), except \CC, diverge from C with different syntax and semantics, only interoperate indirectly with C, and are not systems languages, for those with managed memory. 346 As a result, there is a significant learning curve to move to these languages, and C legacy-code must be rewritten. 347 While \CC, like \CFA, takes an evolutionary approach to extend C, \CC's constantly growing complex and interdependent features-set (\eg objects, inheritance, templates, etc.) mean idiomatic \CC code is difficult to use from C, and C programmers must expend significant effort learning \CC. 348 Hence, rewriting and retraining costs for these languages, even \CC, are prohibitive for companies with a large C software-base. 349 \CFA with its orthogonal feature-set, its high-performance runtime, and direct access to all existing C libraries circumvents these problems. 350 \end{comment} 351 352 \CFA embraces user-level threading, language extensions for advanced control-flow, and safety as the default. 353 We present comparative examples so the reader can judge if the \CFA control-flow extensions are better and safer than those in other concurrent, imperative programming languages, and perform experiments to show the \CFA runtime is competitive with other similar mechanisms. 354 The main contributions of this work are: 355 \begin{itemize}[topsep=3pt,itemsep=1pt] 356 \item 357 language-level generators, coroutines and user-level threading, which respect the expectations of C programmers. 358 \item 359 monitor synchronization without barging, and the ability to safely acquiring multiple monitors \emph{simultaneously} (deadlock free), while seamlessly integrating these capabilities with all monitor synchronization mechanisms. 360 \item 361 providing statically type-safe interfaces that integrate with the \CFA polymorphic type-system and other language features. 362 % \item 363 % library extensions for executors, futures, and actors built on the basic mechanisms. 364 \item 365 a runtime system with no spurious wakeup. 366 \item 367 a dynamic partitioning mechanism to segregate the execution environment for specialized requirements. 368 % \item 369 % a non-blocking I/O library 370 \item 371 experimental results showing comparable performance of the new features with similar mechanisms in other programming languages. 372 \end{itemize} 373 374 Section~\ref{s:StatefulFunction} begins advanced control by introducing sequential functions that retain data and execution state between calls, which produces constructs @generator@ and @coroutine@. 375 Section~\ref{s:Concurrency} begins concurrency, or how to create (fork) and destroy (join) a thread, which produces the @thread@ construct. 376 Section~\ref{s:MutualExclusionSynchronization} discusses the two mechanisms to restricted nondeterminism when controlling shared access to resources (mutual exclusion) and timing relationships among threads (synchronization). 377 Section~\ref{s:Monitor} shows how both mutual exclusion and synchronization are safely embedded in the @monitor@ and @thread@ constructs. 378 Section~\ref{s:CFARuntimeStructure} describes the large-scale mechanism to structure (cluster) threads and virtual processors (kernel threads). 379 Section~\ref{s:Performance} uses a series of microbenchmarks to compare \CFA threading with pthreads, Java OpenJDK-9, Go 1.12.6 and \uC 7.0.0. 380 381 382 \section{Stateful Function} 383 \label{s:StatefulFunction} 384 385 The stateful function is an old idea~\cite{Conway63,Marlin80} that is new again~\cite{C++20Coroutine19}, where execution is temporarily suspended and later resumed, \eg plugin, device driver, finite-state machine. 386 Hence, a stateful function may not end when it returns to its caller, allowing it to be restarted with the data and execution location present at the point of suspension. 387 This capability is accomplished by retaining a data/execution \emph{closure} between invocations. 388 If the closure is fixed size, we call it a \emph{generator} (or \emph{stackless}), and its control flow is restricted, \eg suspending outside the generator is prohibited. 389 If the closure is variable size, we call it a \emph{coroutine} (or \emph{stackful}), and as the names implies, often implemented with a separate stack with no programming restrictions. 390 Hence, refactoring a stackless coroutine may require changing it to stackful. 391 A foundational property of all \emph{stateful functions} is that resume/suspend \emph{do not} cause incremental stack growth, \ie resume/suspend operations are remembered through the closure not the stack. 392 As well, activating a stateful function is \emph{asymmetric} or \emph{symmetric}, identified by resume/suspend (no cycles) and resume/resume (cycles). 393 A fixed closure activated by modified call/return is faster than a variable closure activated by context switching. 394 Additionally, any storage management for the closure (especially in unmanaged languages, \ie no garbage collection) must also be factored into design and performance. 395 Therefore, selecting between stackless and stackful semantics is a tradeoff between programming requirements and performance, where stackless is faster and stackful is more general. 396 Note, creation cost is amortized across usage, so activation cost is usually the dominant factor. 256 This paper provides a minimal concurrency \newterm{Application Program Interface} (API) that is simple, efficient and can be used to build other concurrency features. 257 While the simplest concurrency system is a thread and a lock, this low-level approach is hard to master. 258 An easier approach for programmers is to support higher-level constructs as the basis of concurrency. 259 Indeed, for highly productive concurrent programming, high-level approaches are much more popular~\cite{Hochstein05}. 260 Examples of high-level approaches are task (work) based~\cite{TBB}, implicit threading~\cite{OpenMP}, monitors~\cite{Java}, channels~\cite{CSP,Go}, and message passing~\cite{Erlang,MPI}. 261 262 The following terminology is used. 263 A \newterm{thread} is a fundamental unit of execution that runs a sequence of code and requires a stack to maintain state. 264 Multiple simultaneous threads give rise to \newterm{concurrency}, which requires locking to ensure safe communication and access to shared data. 265 % Correspondingly, concurrency is defined as the concepts and challenges that occur when multiple independent (sharing memory, timing dependencies, \etc) concurrent threads are introduced. 266 \newterm{Locking}, and by extension \newterm{locks}, are defined as a mechanism to prevent progress of threads to provide safety. 267 \newterm{Parallelism} is running multiple threads simultaneously. 268 Parallelism implies \emph{actual} simultaneous execution, where concurrency only requires \emph{apparent} simultaneous execution. 269 As such, parallelism only affects performance, which is observed through differences in space and/or time at runtime. 270 271 Hence, there are two problems to be solved: concurrency and parallelism. 272 While these two concepts are often combined, they are distinct, requiring different tools~\cite[\S~2]{Buhr05a}. 273 Concurrency tools handle synchronization and mutual exclusion, while parallelism tools handle performance, cost and resource utilization. 274 275 The proposed concurrency API is implemented in a dialect of C, called \CFA. 276 The paper discusses how the language features are added to the \CFA translator with respect to parsing, semantic, and type checking, and the corresponding high-performance runtime-library to implement the concurrency features. 277 278 279 \section{\CFA Overview} 280 281 The following is a quick introduction to the \CFA language, specifically tailored to the features needed to support concurrency. 282 Extended versions and explanation of the following code examples are available at the \CFA website~\cite{Cforall} or in Moss~\etal~\cite{Moss18}. 283 284 \CFA is an extension of ISO-C, and hence, supports all C paradigms. 285 %It is a non-object-oriented system-language, meaning most of the major abstractions have either no runtime overhead or can be opted out easily. 286 Like C, the basics of \CFA revolve around structures and routines. 287 Virtually all of the code generated by the \CFA translator respects C memory layouts and calling conventions. 288 While \CFA is not an object-oriented language, lacking the concept of a receiver (\eg @this@) and nominal inheritance-relationships, C does have a notion of objects: ``region of data storage in the execution environment, the contents of which can represent values''~\cite[3.15]{C11}. 289 While some \CFA features are common in object-oriented programming-languages, they are an independent capability allowing \CFA to adopt them while retaining a procedural paradigm. 290 291 292 \subsection{References} 293 294 \CFA provides multi-level rebindable references, as an alternative to pointers, which significantly reduces syntactic noise. 295 \begin{cfa} 296 int x = 1, y = 2, z = 3; 297 int * p1 = &x, ** p2 = &p1, *** p3 = &p2, $\C{// pointers to x}$ 298 `&` r1 = x, `&&` r2 = r1, `&&&` r3 = r2; $\C{// references to x}$ 299 int * p4 = &z, `&` r4 = z; 300 301 *p1 = 3; **p2 = 3; ***p3 = 3; // change x 302 r1 = 3; r2 = 3; r3 = 3; // change x: implicit dereferences *r1, **r2, ***r3 303 **p3 = &y; *p3 = &p4; // change p1, p2 304 `&`r3 = &y; `&&`r3 = &`&`r4; // change r1, r2: cancel implicit dereferences (&*)**r3, (&(&*)*)*r3, &(&*)r4 305 \end{cfa} 306 A reference is a handle to an object, like a pointer, but is automatically dereferenced the specified number of levels. 307 Referencing (address-of @&@) a reference variable cancels one of the implicit dereferences, until there are no more implicit references, after which normal expression behaviour applies. 308 309 310 \subsection{\texorpdfstring{\protect\lstinline{with} Statement}{with Statement}} 311 \label{s:WithStatement} 312 313 Heterogeneous data is aggregated into a structure/union. 314 To reduce syntactic noise, \CFA provides a @with@ statement (see Pascal~\cite[\S~4.F]{Pascal}) to elide aggregate field-qualification by opening a scope containing the field identifiers. 315 \begin{cquote} 316 \vspace*{-\baselineskip}%??? 317 \lstDeleteShortInline@% 318 \begin{cfa} 319 struct S { char c; int i; double d; }; 320 struct T { double m, n; }; 321 // multiple aggregate parameters 322 \end{cfa} 323 \begin{tabular}{@{}l@{\hspace{2\parindentlnth}}|@{\hspace{2\parindentlnth}}l@{}} 324 \begin{cfa} 325 void f( S & s, T & t ) { 326 `s.`c; `s.`i; `s.`d; 327 `t.`m; `t.`n; 328 } 329 \end{cfa} 330 & 331 \begin{cfa} 332 void f( S & s, T & t ) `with ( s, t )` { 333 c; i; d; // no qualification 334 m; n; 335 } 336 \end{cfa} 337 \end{tabular} 338 \lstMakeShortInline@% 339 \end{cquote} 340 Object-oriented programming languages only provide implicit qualification for the receiver. 341 342 In detail, the @with@ statement has the form: 343 \begin{cfa} 344 $\emph{with-statement}$: 345 'with' '(' $\emph{expression-list}$ ')' $\emph{compound-statement}$ 346 \end{cfa} 347 and may appear as the body of a routine or nested within a routine body. 348 Each expression in the expression-list provides a type and object. 349 The type must be an aggregate type. 350 (Enumerations are already opened.) 351 The object is the implicit qualifier for the open structure-fields. 352 All expressions in the expression list are open in parallel within the compound statement, which is different from Pascal, which nests the openings from left to right. 353 354 355 \subsection{Overloading} 356 357 \CFA maximizes the ability to reuse names via overloading to aggressively address the naming problem. 358 Both variables and routines may be overloaded, where selection is based on types, and number of returns (as in Ada~\cite{Ada}) and arguments. 359 \begin{cquote} 360 \vspace*{-\baselineskip}%??? 361 \lstDeleteShortInline@% 362 \begin{cfa} 363 // selection based on type 364 \end{cfa} 365 \begin{tabular}{@{}l@{\hspace{2\parindentlnth}}|@{\hspace{2\parindentlnth}}l@{}} 366 \begin{cfa} 367 const short int `MIN` = -32768; 368 const int `MIN` = -2147483648; 369 const long int `MIN` = -9223372036854775808L; 370 \end{cfa} 371 & 372 \begin{cfa} 373 short int si = `MIN`; 374 int i = `MIN`; 375 long int li = `MIN`; 376 \end{cfa} 377 \end{tabular} 378 \begin{cfa} 379 // selection based on type and number of parameters 380 \end{cfa} 381 \begin{tabular}{@{}l@{\hspace{2.7\parindentlnth}}|@{\hspace{2\parindentlnth}}l@{}} 382 \begin{cfa} 383 void `f`( void ); 384 void `f`( char ); 385 void `f`( int, double ); 386 \end{cfa} 387 & 388 \begin{cfa} 389 `f`(); 390 `f`( 'a' ); 391 `f`( 3, 5.2 ); 392 \end{cfa} 393 \end{tabular} 394 \begin{cfa} 395 // selection based on type and number of returns 396 \end{cfa} 397 \begin{tabular}{@{}l@{\hspace{2\parindentlnth}}|@{\hspace{2\parindentlnth}}l@{}} 398 \begin{cfa} 399 char `f`( int ); 400 double `f`( int ); 401 [char, double] `f`( int ); 402 \end{cfa} 403 & 404 \begin{cfa} 405 char c = `f`( 3 ); 406 double d = `f`( 3 ); 407 [d, c] = `f`( 3 ); 408 \end{cfa} 409 \end{tabular} 410 \lstMakeShortInline@% 411 \end{cquote} 412 Overloading is important for \CFA concurrency since the runtime system relies on creating different types to represent concurrency objects. 413 Therefore, overloading is necessary to prevent the need for long prefixes and other naming conventions to prevent name clashes. 414 As seen in Section~\ref{basics}, routine @main@ is heavily overloaded. 415 416 Variable overloading is useful in the parallel semantics of the @with@ statement for fields with the same name: 417 \begin{cfa} 418 struct S { int `i`; int j; double m; } s; 419 struct T { int `i`; int k; int m; } t; 420 with ( s, t ) { 421 j + k; $\C{// unambiguous, s.j + t.k}$ 422 m = 5.0; $\C{// unambiguous, s.m = 5.0}$ 423 m = 1; $\C{// unambiguous, t.m = 1}$ 424 int a = m; $\C{// unambiguous, a = t.m }$ 425 double b = m; $\C{// unambiguous, b = s.m}$ 426 int c = `s.i` + `t.i`; $\C{// unambiguous, qualification}$ 427 (double)m; $\C{// unambiguous, cast s.m}$ 428 } 429 \end{cfa} 430 For parallel semantics, both @s.i@ and @t.i@ are visible the same type, so only @i@ is ambiguous without qualification. 431 432 433 \subsection{Operators} 434 435 Overloading also extends to operators. 436 Operator-overloading syntax creates a routine name with an operator symbol and question marks for the operands: 437 \begin{cquote} 438 \lstDeleteShortInline@% 439 \begin{tabular}{@{}ll@{\hspace{\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 440 \begin{cfa} 441 int ++? (int op); 442 int ?++ (int op); 443 int `?+?` (int op1, int op2); 444 int ?<=?(int op1, int op2); 445 int ?=? (int & op1, int op2); 446 int ?+=?(int & op1, int op2); 447 \end{cfa} 448 & 449 \begin{cfa} 450 // unary prefix increment 451 // unary postfix increment 452 // binary plus 453 // binary less than 454 // binary assignment 455 // binary plus-assignment 456 \end{cfa} 457 & 458 \begin{cfa} 459 struct S { int i, j; }; 460 S `?+?`( S op1, S op2) { // add two structures 461 return (S){op1.i + op2.i, op1.j + op2.j}; 462 } 463 S s1 = {1, 2}, s2 = {2, 3}, s3; 464 s3 = s1 `+` s2; // compute sum: s3 == {2, 5} 465 \end{cfa} 466 \end{tabular} 467 \lstMakeShortInline@% 468 \end{cquote} 469 While concurrency does not use operator overloading directly, it provides an introduction for the syntax of constructors. 470 471 472 \subsection{Parametric Polymorphism} 473 \label{s:ParametricPolymorphism} 474 475 The signature feature of \CFA is parametric-polymorphic routines~\cite{} with routines generalized using a @forall@ clause (giving the language its name), which allow separately compiled routines to support generic usage over multiple types. 476 For example, the following sum routine works for any type that supports construction from 0 and addition: 477 \begin{cfa} 478 forall( otype T | { void `?{}`( T *, zero_t ); T `?+?`( T, T ); } ) // constraint type, 0 and + 479 T sum( T a[$\,$], size_t size ) { 480 `T` total = { `0` }; $\C{// initialize by 0 constructor}$ 481 for ( size_t i = 0; i < size; i += 1 ) 482 total = total `+` a[i]; $\C{// select appropriate +}$ 483 return total; 484 } 485 S sa[5]; 486 int i = sum( sa, 5 ); $\C{// use S's 0 construction and +}$ 487 \end{cfa} 488 489 \CFA provides \newterm{traits} to name a group of type assertions, where the trait name allows specifying the same set of assertions in multiple locations, preventing repetition mistakes at each routine declaration: 490 \begin{cfa} 491 trait `sumable`( otype T ) { 492 void `?{}`( T &, zero_t ); $\C{// 0 literal constructor}$ 493 T `?+?`( T, T ); $\C{// assortment of additions}$ 494 T ?+=?( T &, T ); 495 T ++?( T & ); 496 T ?++( T & ); 497 }; 498 forall( otype T `| sumable( T )` ) $\C{// use trait}$ 499 T sum( T a[$\,$], size_t size ); 500 \end{cfa} 501 502 Assertions can be @otype@ or @dtype@. 503 @otype@ refers to a ``complete'' object, \ie an object has a size, default constructor, copy constructor, destructor and an assignment operator. 504 @dtype@ only guarantees an object has a size and alignment. 505 506 Using the return type for discrimination, it is possible to write a type-safe @alloc@ based on the C @malloc@: 507 \begin{cfa} 508 forall( dtype T | sized(T) ) T * alloc( void ) { return (T *)malloc( sizeof(T) ); } 509 int * ip = alloc(); $\C{// select type and size from left-hand side}$ 510 double * dp = alloc(); 511 struct S {...} * sp = alloc(); 512 \end{cfa} 513 where the return type supplies the type/size of the allocation, which is impossible in most type systems. 514 515 516 \subsection{Constructors / Destructors} 517 518 Object lifetime is a challenge in non-managed programming languages. 519 \CFA responds with \CC-like constructors and destructors: 520 \begin{cfa} 521 struct VLA { int len, * data; }; $\C{// variable length array of integers}$ 522 void ?{}( VLA & vla ) with ( vla ) { len = 10; data = alloc( len ); } $\C{// default constructor}$ 523 void ?{}( VLA & vla, int size, char fill ) with ( vla ) { len = size; data = alloc( len, fill ); } // initialization 524 void ?{}( VLA & vla, VLA other ) { vla.len = other.len; vla.data = other.data; } $\C{// copy, shallow}$ 525 void ^?{}( VLA & vla ) with ( vla ) { free( data ); } $\C{// destructor}$ 526 { 527 VLA x, y = { 20, 0x01 }, z = y; $\C{// z points to y}$ 528 // x{}; y{ 20, 0x01 }; z{ z, y }; 529 ^x{}; $\C{// deallocate x}$ 530 x{}; $\C{// reallocate x}$ 531 z{ 5, 0xff }; $\C{// reallocate z, not pointing to y}$ 532 ^y{}; $\C{// deallocate y}$ 533 y{ x }; $\C{// reallocate y, points to x}$ 534 x{}; $\C{// reallocate x, not pointing to y}$ 535 // ^z{}; ^y{}; ^x{}; 536 } 537 \end{cfa} 538 Like \CC, construction is implicit on allocation (stack/heap) and destruction is implicit on deallocation. 539 The object and all their fields are constructed/destructed. 540 \CFA also provides @new@ and @delete@, which behave like @malloc@ and @free@, in addition to constructing and destructing objects: 541 \begin{cfa} 542 { struct S s = {10}; $\C{// allocation, call constructor}$ 543 ... 544 } $\C{// deallocation, call destructor}$ 545 struct S * s = new(); $\C{// allocation, call constructor}$ 546 ... 547 delete( s ); $\C{// deallocation, call destructor}$ 548 \end{cfa} 549 \CFA concurrency uses object lifetime as a means of synchronization and/or mutual exclusion. 550 551 552 \section{Concurrency Basics}\label{basics} 553 554 At its core, concurrency is based on multiple call-stacks and scheduling threads executing on these stacks. 555 Multiple call stacks (or contexts) and a single thread of execution, called \newterm{coroutining}~\cite{Conway63,Marlin80}, does \emph{not} imply concurrency~\cite[\S~2]{Buhr05a}. 556 In coroutining, the single thread is self-scheduling across the stacks, so execution is deterministic, \ie given fixed inputs, the execution path to the outputs is fixed and predictable. 557 A \newterm{stackless} coroutine executes on the caller's stack~\cite{Python} but this approach is restrictive, \eg preventing modularization and supporting only iterator/generator-style programming; 558 a \newterm{stackfull} coroutine executes on its own stack, allowing full generality. 559 Only stackfull coroutines are a stepping-stone to concurrency. 560 561 The transition to concurrency, even for execution with a single thread and multiple stacks, occurs when coroutines also context switch to a scheduling oracle, introducing non-determinism from the coroutine perspective~\cite[\S~3]{Buhr05a}. 562 Therefore, a minimal concurrency system is possible using coroutines (see Section \ref{coroutine}) in conjunction with a scheduler to decide where to context switch next. 563 The resulting execution system now follows a cooperative threading-model, called \newterm{non-preemptive scheduling}. 564 565 Because the scheduler is special, it can either be a stackless or stackfull coroutine. 566 For stackless, the scheduler performs scheduling on the stack of the current coroutine and switches directly to the next coroutine, so there is one context switch. 567 For stackfull, the current coroutine switches to the scheduler, which performs scheduling, and it then switches to the next coroutine, so there are two context switches. 568 A stackfull scheduler is often used for simplicity and security, even through there is a slightly higher runtime-cost. 569 570 Regardless of the approach used, a subset of concurrency related challenges start to appear. 571 For the complete set of concurrency challenges to occur, the missing feature is \newterm{preemption}, where context switching occurs randomly between any two instructions, often based on a timer interrupt, called \newterm{preemptive scheduling}. 572 While a scheduler introduces uncertainty in the order of execution, preemption introduces uncertainty where context switches occur. 573 Interestingly, uncertainty is necessary for the runtime (operating) system to give the illusion of parallelism on a single processor and increase performance on multiple processors. 574 The reason is that only the runtime has complete knowledge about resources and how to best utilized them. 575 However, the introduction of unrestricted non-determinism results in the need for \newterm{mutual exclusion} and \newterm{synchronization} to restrict non-determinism for correctness; 576 otherwise, it is impossible to write meaningful programs. 577 Optimal performance in concurrent applications is often obtained by having as much non-determinism as correctness allows. 578 579 580 \subsection{\protect\CFA's Thread Building Blocks} 581 582 An important missing feature in C is threading\footnote{While the C11 standard defines a ``threads.h'' header, it is minimal and defined as optional. 583 As such, library support for threading is far from widespread. 584 At the time of writing the paper, neither \protect\lstinline|gcc| nor \protect\lstinline|clang| support ``threads.h'' in their standard libraries.}. 585 In modern programming languages, a lack of threading is unacceptable~\cite{Sutter05, Sutter05b}, and therefore existing and new programming languages must have tools for writing efficient concurrent programs to take advantage of parallelism. 586 As an extension of C, \CFA needs to express these concepts in a way that is as natural as possible to programmers familiar with imperative languages. 587 Furthermore, because C is a system-level language, programmers expect to choose precisely which features they need and which cost they are willing to pay. 588 Hence, concurrent programs should be written using high-level mechanisms, and only step down to lower-level mechanisms when performance bottlenecks are encountered. 589 590 591 \subsection{Coroutines: A Stepping Stone}\label{coroutine} 592 593 While the focus of this discussion is concurrency and parallelism, it is important to address coroutines, which are a significant building block of a concurrency system. 594 Coroutines are generalized routines allowing execution to be temporarily suspend and later resumed. 595 Hence, unlike a normal routine, a coroutine may not terminate when it returns to its caller, allowing it to be restarted with the values and execution location present at the point of suspension. 596 This capability is accomplish via the coroutine's stack, where suspend/resume context switch among stacks. 597 Because threading design-challenges are present in coroutines, their design effort is relevant, and this effort can be easily exposed to programmers giving them a useful new programming paradigm because a coroutine handles the class of problems that need to retain state between calls, \eg plugins, device drivers, and finite-state machines. 598 Therefore, the core \CFA coroutine-API for has two fundamental features: independent call-stacks and @suspend@/@resume@ operations. 599 600 For example, a problem made easier with coroutines is unbounded generators, \eg generating an infinite sequence of Fibonacci numbers, where Figure~\ref{f:C-fibonacci} shows conventional approaches for writing a Fibonacci generator in C. 601 \begin{displaymath} 602 \mathsf{fib}(n) = \left \{ 603 \begin{array}{ll} 604 0 & n = 0 \\ 605 1 & n = 1 \\ 606 \mathsf{fib}(n-1) + \mathsf{fib}(n-2) & n \ge 2 \\ 607 \end{array} 608 \right. 609 \end{displaymath} 610 Figure~\ref{f:GlobalVariables} illustrates the following problems: 611 unique unencapsulated global variables necessary to retain state between calls; 612 only one Fibonacci generator; 613 execution state must be explicitly retained via explicit state variables. 614 Figure~\ref{f:ExternalState} addresses these issues: 615 unencapsulated program global variables become encapsulated structure variables; 616 unique global variables are replaced by multiple Fibonacci objects; 617 explicit execution state is removed by precomputing the first two Fibonacci numbers and returning $\mathsf{fib}(n-2)$. 397 618 398 619 \begin{figure} 399 620 \centering 621 \newbox\myboxA 400 622 \begin{lrbox}{\myboxA} 401 623 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 402 typedef struct { 403 int fn1, fn; 404 } Fib; 405 #define FibCtor { 1, 0 } 406 int fib( Fib * f ) { 407 408 409 410 int fn = f->fn; f->fn = f->fn1; 411 f->fn1 = f->fn + fn; 624 `int f1, f2, state = 1;` // single global variables 625 int fib() { 626 int fn; 627 `switch ( state )` { // explicit execution state 628 case 1: fn = 0; f1 = fn; state = 2; break; 629 case 2: fn = 1; f2 = f1; f1 = fn; state = 3; break; 630 case 3: fn = f1 + f2; f2 = f1; f1 = fn; break; 631 } 412 632 return fn; 413 414 633 } 415 634 int main() { 416 Fib f1 = FibCtor, f2 = FibCtor; 417 for ( int i = 0; i < 10; i += 1 ) 418 printf( "%d %d\n",419 fib( &f1 ), fib( &f2 ) );635 636 for ( int i = 0; i < 10; i += 1 ) { 637 printf( "%d\n", fib() ); 638 } 420 639 } 421 640 \end{cfa} 422 641 \end{lrbox} 423 642 643 \newbox\myboxB 424 644 \begin{lrbox}{\myboxB} 425 645 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 426 `generator` Fib { 427 int fn1, fn; 428 }; 429 430 void `main(Fib & fib)` with(fib) { 431 432 [fn1, fn] = [1, 0]; 433 for () { 434 `suspend;` 435 [fn1, fn] = [fn, fn + fn1]; 436 646 #define FIB_INIT `{ 0, 1 }` 647 typedef struct { int f2, f1; } Fib; 648 int fib( Fib * f ) { 649 650 int ret = f->f2; 651 int fn = f->f1 + f->f2; 652 f->f2 = f->f1; f->f1 = fn; 653 654 return ret; 655 } 656 int main() { 657 Fib f1 = FIB_INIT, f2 = FIB_INIT; 658 for ( int i = 0; i < 10; i += 1 ) { 659 printf( "%d %d\n", fib( &f1 ), fib( &f2 ) ); 437 660 } 661 } 662 \end{cfa} 663 \end{lrbox} 664 665 \subfloat[3 States: global variables]{\label{f:GlobalVariables}\usebox\myboxA} 666 \qquad 667 \subfloat[1 State: external variables]{\label{f:ExternalState}\usebox\myboxB} 668 \caption{C Fibonacci Implementations} 669 \label{f:C-fibonacci} 670 671 \bigskip 672 673 \newbox\myboxA 674 \begin{lrbox}{\myboxA} 675 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 676 `coroutine` Fib { int fn; }; 677 void main( Fib & fib ) with( fib ) { 678 int f1, f2; 679 fn = 0; f1 = fn; `suspend()`; 680 fn = 1; f2 = f1; f1 = fn; `suspend()`; 681 for ( ;; ) { 682 fn = f1 + f2; f2 = f1; f1 = fn; `suspend()`; 683 } 684 } 685 int next( Fib & fib ) with( fib ) { 686 `resume( fib );` 687 return fn; 438 688 } 439 689 int main() { 440 690 Fib f1, f2; 441 for ( 10 )442 sout | `resume( f1 )`.fn443 | `resume( f2 )`.fn;691 for ( int i = 1; i <= 10; i += 1 ) { 692 sout | next( f1 ) | next( f2 ) | endl; 693 } 444 694 } 445 695 \end{cfa} 446 696 \end{lrbox} 447 448 \begin{lrbox}{\myboxC} 449 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 450 typedef struct { 451 int fn1, fn; void * `next`; 452 } Fib; 453 #define FibCtor { 1, 0, NULL } 454 Fib * comain( Fib * f ) { 455 if ( f->next ) goto *f->next; 456 f->next = &&s1; 457 for ( ;; ) { 458 return f; 459 s1:; int fn = f->fn + f->fn1; 460 f->fn1 = f->fn; f->fn = fn; 461 } 462 } 463 int main() { 464 Fib f1 = FibCtor, f2 = FibCtor; 465 for ( int i = 0; i < 10; i += 1 ) 466 printf("%d %d\n",comain(&f1)->fn, 467 comain(&f2)->fn); 468 } 469 \end{cfa} 470 \end{lrbox} 471 472 \subfloat[C asymmetric generator]{\label{f:CFibonacci}\usebox\myboxA} 473 \hspace{3pt} 474 \vrule 475 \hspace{3pt} 476 \subfloat[\CFA asymmetric generator]{\label{f:CFAFibonacciGen}\usebox\myboxB} 477 \hspace{3pt} 478 \vrule 479 \hspace{3pt} 480 \subfloat[C generator implementation]{\label{f:CFibonacciSim}\usebox\myboxC} 481 \caption{Fibonacci (output) asymmetric generator} 482 \label{f:FibonacciAsymmetricGenerator} 483 484 \bigskip 485 486 \begin{lrbox}{\myboxA} 487 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 488 `generator Fmt` { 489 char ch; 490 int g, b; 491 }; 492 void ?{}( Fmt & fmt ) { `resume(fmt);` } // constructor 493 void ^?{}( Fmt & f ) with(f) { $\C[1.75in]{// destructor}$ 494 if ( g != 0 || b != 0 ) sout | nl; } 495 void `main( Fmt & f )` with(f) { 496 for () { $\C{// until destructor call}$ 497 for ( ; g < 5; g += 1 ) { $\C{// groups}$ 498 for ( ; b < 4; b += 1 ) { $\C{// blocks}$ 499 `suspend;` $\C{// wait for character}$ 500 while ( ch == '\n' ) `suspend;` // ignore 501 sout | ch; // newline 502 } sout | " "; // block spacer 503 } sout | nl; // group newline 504 } 505 } 506 int main() { 507 Fmt fmt; $\C{// fmt constructor called}$ 508 for () { 509 sin | fmt.ch; $\C{// read into generator}$ 510 if ( eof( sin ) ) break; 511 `resume( fmt );` 512 } 513 514 } $\C{// fmt destructor called}\CRT$ 515 \end{cfa} 516 \end{lrbox} 517 697 \newbox\myboxB 518 698 \begin{lrbox}{\myboxB} 519 699 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 520 typedef struct { 521 void * next; 522 char ch; 523 int g, b; 524 } Fmt; 525 void comain( Fmt * f ) { 526 if ( f->next ) goto *f->next; 527 f->next = &&s1; 700 `coroutine` Fib { int ret; }; 701 void main( Fib & f ) with( fib ) { 702 int fn, f1 = 1, f2 = 0; 528 703 for ( ;; ) { 529 for ( f->g = 0; f->g < 5; f->g += 1 ) { 530 for ( f->b = 0; f->b < 4; f->b += 1 ) { 531 return; 532 s1:; while ( f->ch == '\n' ) return; 533 printf( "%c", f->ch ); 534 } printf( " " ); 535 } printf( "\n" ); 704 ret = f2; 705 706 fn = f1 + f2; f2 = f1; f1 = fn; `suspend();` 536 707 } 537 708 } 538 int main() { 539 Fmt fmt = { NULL }; comain( &fmt ); // prime 540 for ( ;; ) { 541 scanf( "%c", &fmt.ch ); 542 if ( feof( stdin ) ) break; 543 comain( &fmt ); 544 } 545 if ( fmt.g != 0 || fmt.b != 0 ) printf( "\n" ); 546 } 709 int next( Fib & fib ) with( fib ) { 710 `resume( fib );` 711 return ret; 712 } 713 714 715 716 717 718 547 719 \end{cfa} 548 720 \end{lrbox} 549 550 \subfloat[\CFA asymmetric generator]{\label{f:CFAFormatGen}\usebox\myboxA} 551 \hspace{3pt} 552 \vrule 553 \hspace{3pt} 554 \subfloat[C generator simulation]{\label{f:CFormatSim}\usebox\myboxB} 555 \hspace{3pt} 556 \caption{Formatter (input) asymmetric generator} 557 \label{f:FormatterAsymmetricGenerator} 721 \subfloat[3 States, internal variables]{\label{f:Coroutine3States}\usebox\myboxA} 722 \qquad\qquad 723 \subfloat[1 State, internal variables]{\label{f:Coroutine1State}\usebox\myboxB} 724 \caption{\CFA Coroutine Fibonacci Implementations} 725 \label{f:fibonacci-cfa} 558 726 \end{figure} 559 727 560 Stateful functions appear as generators, coroutines, and threads, where presentations are based on function objects or pointers~\cite{Butenhof97, C++14, MS:VisualC++, BoostCoroutines15}. 561 For example, Python presents generators as a function object: 562 \begin{python} 563 def Gen(): 564 ... `yield val` ... 565 gen = Gen() 566 for i in range( 10 ): 567 print( next( gen ) ) 568 \end{python} 569 Boost presents coroutines in terms of four functor object-types: 570 \begin{cfa} 571 asymmetric_coroutine<>::pull_type 572 asymmetric_coroutine<>::push_type 573 symmetric_coroutine<>::call_type 574 symmetric_coroutine<>::yield_type 575 \end{cfa} 576 and many languages present threading using function pointers, @pthreads@~\cite{Butenhof97}, \Csharp~\cite{Csharp}, Go~\cite{Go}, and Scala~\cite{Scala}, \eg pthreads: 577 \begin{cfa} 578 void * rtn( void * arg ) { ... } 579 int i = 3, rc; 580 pthread_t t; $\C{// thread id}$ 581 `rc = pthread_create( &t, rtn, (void *)i );` $\C{// create and initialized task, type-unsafe input parameter}$ 582 \end{cfa} 583 % void mycor( pthread_t cid, void * arg ) { 584 % int * value = (int *)arg; $\C{// type unsafe, pointer-size only}$ 585 % // thread body 586 % } 587 % int main() { 588 % int input = 0, output; 589 % coroutine_t cid = coroutine_create( &mycor, (void *)&input ); $\C{// type unsafe, pointer-size only}$ 590 % coroutine_resume( cid, (void *)input, (void **)&output ); $\C{// type unsafe, pointer-size only}$ 591 % } 592 \CFA's preferred presentation model for generators/coroutines/threads is a hybrid of objects and functions, with an object-oriented flavour. 593 Essentially, the generator/coroutine/thread function is semantically coupled with a generator/coroutine/thread custom type. 594 The custom type solves several issues, while accessing the underlying mechanisms used by the custom types is still allowed. 595 596 597 \subsection{Generator} 598 599 Stackless generators have the potential to be very small and fast, \ie as small and fast as function call/return for both creation and execution. 600 The \CFA goal is to achieve this performance target, possibly at the cost of some semantic complexity. 601 A series of different kinds of generators and their implementation demonstrate how this goal is accomplished. 602 603 Figure~\ref{f:FibonacciAsymmetricGenerator} shows an unbounded asymmetric generator for an infinite sequence of Fibonacci numbers written in C and \CFA, with a simple C implementation for the \CFA version. 604 This generator is an \emph{output generator}, producing a new result on each resumption. 605 To compute Fibonacci, the previous two values in the sequence are retained to generate the next value, \ie @fn1@ and @fn@, plus the execution location where control restarts when the generator is resumed, \ie top or middle. 606 An additional requirement is the ability to create an arbitrary number of generators (of any kind), \ie retaining one state in global variables is insufficient; 607 hence, state is retained in a closure between calls. 608 Figure~\ref{f:CFibonacci} shows the C approach of manually creating the closure in structure @Fib@, and multiple instances of this closure provide multiple Fibonacci generators. 609 The C version only has the middle execution state because the top execution state is declaration initialization. 610 Figure~\ref{f:CFAFibonacciGen} shows the \CFA approach, which also has a manual closure, but replaces the structure with a custom \CFA @generator@ type. 611 This generator type is then connected to a function that \emph{must be named \lstinline|main|},\footnote{ 612 The name \lstinline|main| has special meaning in C, specifically the function where a program starts execution. 613 Hence, overloading this name for other starting points (generator/coroutine/thread) is a logical extension.} 614 called a \emph{generator main},which takes as its only parameter a reference to the generator type. 615 The generator main contains @suspend@ statements that suspend execution without ending the generator versus @return@. 616 For the Fibonacci generator-main,\footnote{ 617 The \CFA \lstinline|with| opens an aggregate scope making its fields directly accessible, like Pascal \lstinline|with|, but using parallel semantics. 618 Multiple aggregates may be opened.} 619 the top initialization state appears at the start and the middle execution state is denoted by statement @suspend@. 620 Any local variables in @main@ \emph{are not retained} between calls; 621 hence local variables are only for temporary computations \emph{between} suspends. 622 All retained state \emph{must} appear in the generator's type. 623 As well, generator code containing a @suspend@ cannot be refactored into a helper function called by the generator, because @suspend@ is implemented via @return@, so a return from the helper function goes back to the current generator not the resumer. 624 The generator is started by calling function @resume@ with a generator instance, which begins execution at the top of the generator main, and subsequent @resume@ calls restart the generator at its point of last suspension. 625 Resuming an ended (returned) generator is undefined. 626 Function @resume@ returns its argument generator so it can be cascaded in an expression, in this case to print the next Fibonacci value @fn@ computed in the generator instance. 627 Figure~\ref{f:CFibonacciSim} shows the C implementation of the \CFA generator only needs one additional field, @next@, to handle retention of execution state. 628 The computed @goto@ at the start of the generator main, which branches after the previous suspend, adds very little cost to the resume call. 629 Finally, an explicit generator type provides both design and performance benefits, such as multiple type-safe interface functions taking and returning arbitrary types.\footnote{ 630 The \CFA operator syntax uses \lstinline|?| to denote operands, which allows precise definitions for pre, post, and infix operators, \eg \lstinline|++?|, \lstinline|?++|, and \lstinline|?+?|, in addition \lstinline|?\{\}| denotes a constructor, as in \lstinline|foo `f` = `\{`...`\}`|, \lstinline|^?\{\}| denotes a destructor, and \lstinline|?()| is \CC function call \lstinline|operator()|. 631 }% 632 \begin{cfa} 633 int ?()( Fib & fib ) { return `resume( fib )`.fn; } $\C[3.9in]{// function-call interface}$ 634 int ?()( Fib & fib, int N ) { for ( N - 1 ) `fib()`; return `fib()`; } $\C{// use function-call interface to skip N values}$ 635 double ?()( Fib & fib ) { return (int)`fib()` / 3.14159; } $\C{// different return type, cast prevents recursive call}\CRT$ 636 sout | (int)f1() | (double)f1() | f2( 2 ); // alternative interface, cast selects call based on return type, step 2 values 637 \end{cfa} 638 Now, the generator can be a separately compiled opaque-type only accessed through its interface functions. 639 For contrast, Figure~\ref{f:PythonFibonacci} shows the equivalent Python Fibonacci generator, which does not use a generator type, and hence only has a single interface, but an implicit closure. 640 641 Having to manually create the generator closure by moving local-state variables into the generator type is an additional programmer burden. 642 (This restriction is removed by the coroutine in Section~\ref{s:Coroutine}.) 643 This requirement follows from the generality of variable-size local-state, \eg local state with a variable-length array requires dynamic allocation because the array size is unknown at compile time. 644 However, dynamic allocation significantly increases the cost of generator creation/destruction and is a showstopper for embedded real-time programming. 645 But more importantly, the size of the generator type is tied to the local state in the generator main, which precludes separate compilation of the generator main, \ie a generator must be inlined or local state must be dynamically allocated. 646 With respect to safety, we believe static analysis can discriminate local state from temporary variables in a generator, \ie variable usage spanning @suspend@, and generate a compile-time error. 647 Finally, our current experience is that most generator problems have simple data state, including local state, but complex execution state, so the burden of creating the generator type is small. 648 As well, C programmers are not afraid of this kind of semantic programming requirement, if it results in very small, fast generators. 649 650 Figure~\ref{f:CFAFormatGen} shows an asymmetric \newterm{input generator}, @Fmt@, for restructuring text into groups of characters of fixed-size blocks, \ie the input on the left is reformatted into the output on the right, where newlines are ignored. 651 \begin{center} 728 Using a coroutine, it is possible to express the Fibonacci formula directly without any of the C problems. 729 Figure~\ref{f:Coroutine3States} creates a @coroutine@ type: 730 \begin{cfa} 731 `coroutine` Fib { int fn; }; 732 \end{cfa} 733 which provides communication, @fn@, for the \newterm{coroutine main}, @main@, which runs on the coroutine stack, and possibly multiple interface routines @next@. 734 Like the structure in Figure~\ref{f:ExternalState}, the coroutine type allows multiple instances, where instances of this type are passed to the (overloaded) coroutine main. 735 The coroutine main's stack holds the state for the next generation, @f1@ and @f2@, and the code has the three suspend points, representing the three states in the Fibonacci formula, to context switch back to the caller's resume. 736 The interface routine @next@, takes a Fibonacci instance and context switches to it using @resume@; 737 on restart, the Fibonacci field, @fn@, contains the next value in the sequence, which is returned. 738 The first @resume@ is special because it cocalls the coroutine at its coroutine main and allocates the stack; 739 when the coroutine main returns, its stack is deallocated. 740 Hence, @Fib@ is an object at creation, transitions to a coroutine on its first resume, and transitions back to an object when the coroutine main finishes. 741 Figure~\ref{f:Coroutine1State} shows the coroutine version of the C version in Figure~\ref{f:ExternalState}. 742 Coroutine generators are called \newterm{output coroutines} because values are only returned. 743 744 Figure~\ref{f:CFAFmt} shows an \newterm{input coroutine}, @Format@, for restructuring text into groups of characters of fixed-size blocks. 745 For example, the input of the left is reformatted into the output on the right. 746 \begin{quote} 652 747 \tt 653 748 \begin{tabular}{@{}l|l@{}} 654 749 \multicolumn{1}{c|}{\textbf{\textrm{input}}} & \multicolumn{1}{c}{\textbf{\textrm{output}}} \\ 655 \begin{tabular}[t]{@{}ll@{}} 656 abcdefghijklmnopqrstuvwxyz \\ 657 abcdefghijklmnopqrstuvwxyz 658 \end{tabular} 750 abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz 659 751 & 660 752 \begin{tabular}[t]{@{}lllll@{}} … … 664 756 \end{tabular} 665 757 \end{tabular} 666 \end{center} 667 The example takes advantage of resuming a generator in the constructor to prime the loops so the first character sent for formatting appears inside the nested loops. 668 The destructor provides a newline, if formatted text ends with a full line. 669 Figure~\ref{f:CFormatSim} shows the C implementation of the \CFA input generator with one additional field and the computed @goto@. 670 For contrast, Figure~\ref{f:PythonFormatter} shows the equivalent Python format generator with the same properties as the Fibonacci generator. 671 672 Figure~\ref{f:DeviceDriverGen} shows a \emph{killer} asymmetric generator, a device-driver, because device drivers caused 70\%-85\% of failures in Windows/Linux~\cite{Swift05}. 673 Device drives follow the pattern of simple data state but complex execution state, \ie finite state-machine (FSM) parsing a protocol. 674 For example, the following protocol: 675 \begin{center} 676 \ldots\, STX \ldots\, message \ldots\, ESC ETX \ldots\, message \ldots\, ETX 2-byte crc \ldots 677 \end{center} 678 is a network message beginning with the control character STX, ending with an ETX, and followed by a 2-byte cyclic-redundancy check. 679 Control characters may appear in a message if preceded by an ESC. 680 When a message byte arrives, it triggers an interrupt, and the operating system services the interrupt by calling the device driver with the byte read from a hardware register. 681 The device driver returns a status code of its current state, and when a complete message is obtained, the operating system knows the message is in the message buffer. 682 Hence, the device driver is an input/output generator. 683 684 Note, the cost of creating and resuming the device-driver generator, @Driver@, is virtually identical to call/return, so performance in an operating-system kernel is excellent. 685 As well, the data state is small, where variables @byte@ and @msg@ are communication variables for passing in message bytes and returning the message, and variables @lnth@, @crc@, and @sum@ are local variable that must be retained between calls and are manually hoisted into the generator type. 686 % Manually, detecting and hoisting local-state variables is easy when the number is small. 687 In contrast, the execution state is large, with one @resume@ and seven @suspend@s. 688 Hence, the key benefits of the generator are correctness, safety, and maintenance because the execution states are transcribed directly into the programming language rather than using a table-driven approach. 689 Because FSMs can be complex and frequently occur in important domains, direct generator support is important in a system programming language. 758 \end{quote} 759 The example takes advantage of resuming a coroutine in the constructor to prime the loops so the first character sent for formatting appears inside the nested loops. 760 The destruction provides a newline if formatted text ends with a full line. 761 Figure~\ref{f:CFmt} shows the C equivalent formatter, where the loops of the coroutine are flatten (linearized) and rechecked on each call because execution location is not retained between calls. 690 762 691 763 \begin{figure} … … 693 765 \newbox\myboxA 694 766 \begin{lrbox}{\myboxA} 695 \begin{python}[aboveskip=0pt,belowskip=0pt]696 def Fib():697 fn1, fn = 0, 1698 while True:699 `yield fn1`700 fn1, fn = fn, fn1 + fn701 f1 = Fib()702 f2 = Fib()703 for i in range( 10 ):704 print( next( f1 ), next( f2 ) )705 706 707 708 709 710 711 \end{python}712 \end{lrbox}713 714 \newbox\myboxB715 \begin{lrbox}{\myboxB}716 \begin{python}[aboveskip=0pt,belowskip=0pt]717 def Fmt():718 try:719 while True:720 for g in range( 5 ):721 for b in range( 4 ):722 print( `(yield)`, end='' )723 print( ' ', end='' )724 print()725 except GeneratorExit:726 if g != 0 | b != 0:727 print()728 fmt = Fmt()729 `next( fmt )` # prime, next prewritten730 for i in range( 41 ):731 `fmt.send( 'a' );` # send to yield732 \end{python}733 \end{lrbox}734 \subfloat[Fibonacci]{\label{f:PythonFibonacci}\usebox\myboxA}735 \hspace{3pt}736 \vrule737 \hspace{3pt}738 \subfloat[Formatter]{\label{f:PythonFormatter}\usebox\myboxB}739 \caption{Python generator}740 \label{f:PythonGenerator}741 742 \bigskip743 744 \begin{tabular}{@{}l|l@{}}745 767 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 746 enum Status { CONT, MSG, ESTX, 747 ELNTH, ECRC }; 748 `generator` Driver { 749 Status status; 750 unsigned char byte, * msg; // communication 751 unsigned int lnth, sum; // local state 752 unsigned short int crc; 768 `coroutine` Format { 769 char ch; // used for communication 770 int g, b; // global because used in destructor 753 771 }; 754 void ?{}( Driver & d, char * m ) { d.msg = m; } 755 Status next( Driver & d, char b ) with( d ) { 756 byte = b; `resume( d );` return status; 757 } 758 void main( Driver & d ) with( d ) { 759 enum { STX = '\002', ESC = '\033', 760 ETX = '\003', MaxMsg = 64 }; 761 msg: for () { // parse message 762 status = CONT; 763 lnth = 0; sum = 0; 764 while ( byte != STX ) `suspend;` 765 emsg: for () { 766 `suspend;` // process byte 767 \end{cfa} 768 & 769 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 770 choose ( byte ) { // switch with implicit break 771 case STX: 772 status = ESTX; `suspend;` continue msg; 773 case ETX: 774 break emsg; 775 case ESC: 776 `suspend;` 772 void main( Format & fmt ) with( fmt ) { 773 for ( ;; ) { 774 for ( g = 0; g < 5; g += 1 ) { // group 775 for ( b = 0; b < 4; b += 1 ) { // block 776 `suspend();` 777 sout | ch; // separator 777 778 } 778 if ( lnth >= MaxMsg ) { // buffer full ? 779 status = ELNTH; `suspend;` continue msg; } 780 msg[lnth++] = byte; 781 sum += byte; 779 sout | " "; // separator 782 780 } 783 msg[lnth] = '\0'; // terminate string 784 `suspend;` 785 crc = byte << 8; 786 `suspend;` 787 status = (crc | byte) == sum ? MSG : ECRC; 788 `suspend;` 781 sout | endl; 789 782 } 790 783 } 791 \end{cfa} 792 \end{tabular} 793 \caption{Device-driver generator for communication protocol} 794 \label{f:DeviceDriverGen} 795 \end{figure} 796 797 Figure~\ref{f:CFAPingPongGen} shows a symmetric generator, where the generator resumes another generator, forming a resume/resume cycle. 798 (The trivial cycle is a generator resuming itself.) 799 This control flow is similar to recursion for functions but without stack growth. 800 The steps for symmetric control-flow are creating, executing, and terminating the cycle. 801 Constructing the cycle must deal with definition-before-use to close the cycle, \ie, the first generator must know about the last generator, which is not within scope. 802 (This issue occurs for any cyclic data structure.) 803 % The example creates all the generators and then assigns the partners that form the cycle. 804 % Alternatively, the constructor can assign the partners as they are declared, except the first, and the first-generator partner is set after the last generator declaration to close the cycle. 805 Once the cycle is formed, the program main resumes one of the generators, and the generators can then traverse an arbitrary cycle using @resume@ to activate partner generator(s). 806 Terminating the cycle is accomplished by @suspend@ or @return@, both of which go back to the stack frame that started the cycle (program main in the example). 807 The starting stack-frame is below the last active generator because the resume/resume cycle does not grow the stack. 808 Also, since local variables are not retained in the generator function, it does not contain any objects with destructors that must be called, so the cost is the same as a function return. 809 Destructor cost occurs when the generator instance is deallocated, which is easily controlled by the programmer. 810 811 Figure~\ref{f:CPingPongSim} shows the implementation of the symmetric generator, where the complexity is the @resume@, which needs an extension to the calling convention to perform a forward rather than backward jump. 812 This jump-starts at the top of the next generator main to re-execute the normal calling convention to make space on the stack for its local variables. 813 However, before the jump, the caller must reset its stack (and any registers) equivalent to a @return@, but subsequently jump forward. 814 This semantics is basically a tail-call optimization, which compilers already perform. 815 The example shows the assembly code to undo the generator's entry code before the direct jump. 816 This assembly code depends on what entry code is generated, specifically if there are local variables and the level of optimization. 817 To provide this new calling convention requires a mechanism built into the compiler, which is beyond the scope of \CFA at this time. 818 Nevertheless, it is possible to hand generate any symmetric generators for proof of concept and performance testing. 819 A compiler could also eliminate other artifacts in the generator simulation to further increase performance, \eg LLVM has various coroutine support~\cite{CoroutineTS}, and \CFA can leverage this support should it fork @clang@. 820 821 \begin{figure} 822 \centering 823 \begin{lrbox}{\myboxA} 824 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 825 `generator PingPong` { 826 const char * name; 827 int N; 828 int i; // local state 829 PingPong & partner; // rebindable reference 830 }; 831 832 void `main( PingPong & pp )` with(pp) { 833 for ( ; i < N; i += 1 ) { 834 sout | name | i; 835 `resume( partner );` 836 } 784 void ?{}( Format & fmt ) { `resume( fmt );` } 785 void ^?{}( Format & fmt ) with( fmt ) { 786 if ( g != 0 || b != 0 ) sout | endl; 787 } 788 void format( Format & fmt ) { 789 `resume( fmt );` 837 790 } 838 791 int main() { 839 enum { N = 5 }; 840 PingPong ping = {"ping",N,0}, pong = {"pong",N,0}; 841 &ping.partner = &pong; &pong.partner = &ping; 842 `resume( ping );` 843 } 844 \end{cfa} 845 \end{lrbox} 846 847 \begin{lrbox}{\myboxB} 848 \begin{cfa}[escapechar={},aboveskip=0pt,belowskip=0pt] 849 typedef struct PingPong { 850 const char * name; 851 int N, i; 852 struct PingPong * partner; 853 void * next; 854 } PingPong; 855 #define PPCtor(name, N) {name,N,0,NULL,NULL} 856 void comain( PingPong * pp ) { 857 if ( pp->next ) goto *pp->next; 858 pp->next = &&cycle; 859 for ( ; pp->i < pp->N; pp->i += 1 ) { 860 printf( "%s %d\n", pp->name, pp->i ); 861 asm( "mov %0,%%rdi" : "=m" (pp->partner) ); 862 asm( "mov %rdi,%rax" ); 863 asm( "popq %rbx" ); 864 asm( "jmp comain" ); 865 cycle: ; 866 } 867 } 868 \end{cfa} 869 \end{lrbox} 870 871 \subfloat[\CFA symmetric generator]{\label{f:CFAPingPongGen}\usebox\myboxA} 872 \hspace{3pt} 873 \vrule 874 \hspace{3pt} 875 \subfloat[C generator simulation]{\label{f:CPingPongSim}\usebox\myboxB} 876 \hspace{3pt} 877 \caption{Ping-Pong symmetric generator} 878 \label{f:PingPongSymmetricGenerator} 879 \end{figure} 880 881 Finally, part of this generator work was inspired by the recent \CCtwenty generator proposal~\cite{C++20Coroutine19} (which they call coroutines). 882 Our work provides the same high-performance asymmetric generators as \CCtwenty, and extends their work with symmetric generators. 883 An additional \CCtwenty generator feature allows @suspend@ and @resume@ to be followed by a restricted compound statement that is executed after the current generator has reset its stack but before calling the next generator, specified with \CFA syntax: 884 \begin{cfa} 885 ... suspend`{ ... }`; 886 ... resume( C )`{ ... }` ... 887 \end{cfa} 888 Since the current generator's stack is released before calling the compound statement, the compound statement can only reference variables in the generator's type. 889 This feature is useful when a generator is used in a concurrent context to ensure it is stopped before releasing a lock in the compound statement, which might immediately allow another thread to resume the generator. 890 Hence, this mechanism provides a general and safe handoff of the generator among competing threads. 891 892 893 \subsection{Coroutine} 894 \label{s:Coroutine} 895 896 Stackful coroutines extend generator semantics, \ie there is an implicit closure and @suspend@ may appear in a helper function called from the coroutine main. 897 A coroutine is specified by replacing @generator@ with @coroutine@ for the type. 898 Coroutine generality results in higher cost for creation, due to dynamic stack allocation, execution, due to context switching among stacks, and terminating, due to possible stack unwinding and dynamic stack deallocation. 899 A series of different kinds of coroutines and their implementations demonstrate how coroutines extend generators. 900 901 First, the previous generator examples are converted to their coroutine counterparts, allowing local-state variables to be moved from the generator type into the coroutine main. 902 \begin{description} 903 \item[Fibonacci] 904 Move the declaration of @fn1@ to the start of coroutine main. 905 \begin{cfa}[xleftmargin=0pt] 906 void main( Fib & fib ) with(fib) { 907 `int fn1;` 908 \end{cfa} 909 \item[Formatter] 910 Move the declaration of @g@ and @b@ to the for loops in the coroutine main. 911 \begin{cfa}[xleftmargin=0pt] 912 for ( `g`; 5 ) { 913 for ( `b`; 4 ) { 914 \end{cfa} 915 \item[Device Driver] 916 Move the declaration of @lnth@ and @sum@ to their points of initialization. 917 \begin{cfa}[xleftmargin=0pt] 918 status = CONT; 919 `unsigned int lnth = 0, sum = 0;` 920 ... 921 `unsigned short int crc = byte << 8;` 922 \end{cfa} 923 \item[PingPong] 924 Move the declaration of @i@ to the for loop in the coroutine main. 925 \begin{cfa}[xleftmargin=0pt] 926 void main( PingPong & pp ) with(pp) { 927 for ( `i`; N ) { 928 \end{cfa} 929 \end{description} 930 It is also possible to refactor code containing local-state and @suspend@ statements into a helper function, like the computation of the CRC for the device driver. 931 \begin{cfa} 932 unsigned int Crc() { 933 `suspend;` 934 unsigned short int crc = byte << 8; 935 `suspend;` 936 status = (crc | byte) == sum ? MSG : ECRC; 937 return crc; 938 } 939 \end{cfa} 940 A call to this function is placed at the end of the driver's coroutine-main. 941 For complex finite-state machines, refactoring is part of normal program abstraction, especially when code is used in multiple places. 942 Again, this complexity is usually associated with execution state rather than data state. 943 944 \begin{comment} 945 Figure~\ref{f:Coroutine3States} creates a @coroutine@ type, @`coroutine` Fib { int fn; }@, which provides communication, @fn@, for the \newterm{coroutine main}, @main@, which runs on the coroutine stack, and possibly multiple interface functions, \eg @next@. 946 Like the structure in Figure~\ref{f:ExternalState}, the coroutine type allows multiple instances, where instances of this type are passed to the (overloaded) coroutine main. 947 The coroutine main's stack holds the state for the next generation, @f1@ and @f2@, and the code represents the three states in the Fibonacci formula via the three suspend points, to context switch back to the caller's @resume@. 948 The interface function @next@, takes a Fibonacci instance and context switches to it using @resume@; 949 on restart, the Fibonacci field, @fn@, contains the next value in the sequence, which is returned. 950 The first @resume@ is special because it allocates the coroutine stack and cocalls its coroutine main on that stack; 951 when the coroutine main returns, its stack is deallocated. 952 Hence, @Fib@ is an object at creation, transitions to a coroutine on its first resume, and transitions back to an object when the coroutine main finishes. 953 Figure~\ref{f:Coroutine1State} shows the coroutine version of the C version in Figure~\ref{f:ExternalState}. 954 Coroutine generators are called \newterm{output coroutines} because values are only returned. 955 956 \begin{figure} 957 \centering 958 \newbox\myboxA 959 % \begin{lrbox}{\myboxA} 960 % \begin{cfa}[aboveskip=0pt,belowskip=0pt] 961 % `int fn1, fn2, state = 1;` // single global variables 962 % int fib() { 963 % int fn; 964 % `switch ( state )` { // explicit execution state 965 % case 1: fn = 0; fn1 = fn; state = 2; break; 966 % case 2: fn = 1; fn2 = fn1; fn1 = fn; state = 3; break; 967 % case 3: fn = fn1 + fn2; fn2 = fn1; fn1 = fn; break; 968 % } 969 % return fn; 970 % } 971 % int main() { 972 % 973 % for ( int i = 0; i < 10; i += 1 ) { 974 % printf( "%d\n", fib() ); 975 % } 976 % } 977 % \end{cfa} 978 % \end{lrbox} 979 \begin{lrbox}{\myboxA} 980 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 981 #define FibCtor { 0, 1 } 982 typedef struct { int fn1, fn; } Fib; 983 int fib( Fib * f ) { 984 985 int ret = f->fn1; 986 f->fn1 = f->fn; 987 f->fn = ret + f->fn; 988 return ret; 989 } 990 991 992 993 int main() { 994 Fib f1 = FibCtor, f2 = FibCtor; 995 for ( int i = 0; i < 10; i += 1 ) { 996 printf( "%d %d\n", 997 fib( &f1 ), fib( &f2 ) ); 792 Format fmt; 793 eof: for ( ;; ) { 794 sin | fmt.ch; 795 if ( eof( sin ) ) break eof; 796 format( fmt ); 998 797 } 999 798 } … … 1004 803 \begin{lrbox}{\myboxB} 1005 804 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1006 `coroutine` Fib { int fn1; }; 1007 void main( Fib & fib ) with( fib ) { 1008 int fn; 1009 [fn1, fn] = [0, 1]; 1010 for () { 1011 `suspend;` 1012 [fn1, fn] = [fn, fn1 + fn]; 805 struct Format { 806 char ch; 807 int g, b; 808 }; 809 void format( struct Format * fmt ) { 810 if ( fmt->ch != -1 ) { // not EOF ? 811 printf( "%c", fmt->ch ); 812 fmt->b += 1; 813 if ( fmt->b == 4 ) { // block 814 printf( " " ); // separator 815 fmt->b = 0; 816 fmt->g += 1; 817 } 818 if ( fmt->g == 5 ) { // group 819 printf( "\n" ); // separator 820 fmt->g = 0; 821 } 822 } else { 823 if ( fmt->g != 0 || fmt->b != 0 ) printf( "\n" ); 1013 824 } 1014 825 } 1015 int ?()( Fib & fib ) with( fib ) {1016 return `resume( fib )`.fn1;1017 }1018 826 int main() { 1019 Fib f1, f2; 1020 for ( 10 ) { 1021 sout | f1() | f2(); 1022 } 1023 1024 827 struct Format fmt = { 0, 0, 0 }; 828 for ( ;; ) { 829 scanf( "%c", &fmt.ch ); 830 if ( feof( stdin ) ) break; 831 format( &fmt ); 832 } 833 fmt.ch = -1; 834 format( &fmt ); 835 } 1025 836 \end{cfa} 1026 837 \end{lrbox} 1027 1028 \newbox\myboxC 1029 \begin{lrbox}{\myboxC} 1030 \begin{python}[aboveskip=0pt,belowskip=0pt] 1031 1032 def Fib(): 1033 1034 fn1, fn = 0, 1 1035 while True: 1036 `yield fn1` 1037 fn1, fn = fn, fn1 + fn 1038 1039 1040 // next prewritten 1041 1042 1043 f1 = Fib() 1044 f2 = Fib() 1045 for i in range( 10 ): 1046 print( next( f1 ), next( f2 ) ) 1047 1048 1049 1050 \end{python} 1051 \end{lrbox} 1052 1053 \subfloat[C]{\label{f:GlobalVariables}\usebox\myboxA} 1054 \hspace{3pt} 1055 \vrule 1056 \hspace{3pt} 1057 \subfloat[\CFA]{\label{f:ExternalState}\usebox\myboxB} 1058 \hspace{3pt} 1059 \vrule 1060 \hspace{3pt} 1061 \subfloat[Python]{\label{f:ExternalState}\usebox\myboxC} 1062 \caption{Fibonacci generator} 1063 \label{f:C-fibonacci} 838 \subfloat[\CFA Coroutine]{\label{f:CFAFmt}\usebox\myboxA} 839 \qquad 840 \subfloat[C Linearized]{\label{f:CFmt}\usebox\myboxB} 841 \caption{Formatting text into lines of 5 blocks of 4 characters.} 842 \label{f:fmt-line} 1064 843 \end{figure} 1065 844 1066 \bigskip 1067 1068 \newbox\myboxA 1069 \begin{lrbox}{\myboxA} 1070 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1071 `coroutine` Fib { int fn; }; 1072 void main( Fib & fib ) with( fib ) { 1073 fn = 0; int fn1 = fn; `suspend`; 1074 fn = 1; int fn2 = fn1; fn1 = fn; `suspend`; 1075 for () { 1076 fn = fn1 + fn2; fn2 = fn1; fn1 = fn; `suspend`; } 1077 } 1078 int next( Fib & fib ) with( fib ) { `resume( fib );` return fn; } 1079 int main() { 1080 Fib f1, f2; 1081 for ( 10 ) 1082 sout | next( f1 ) | next( f2 ); 1083 } 1084 \end{cfa} 1085 \end{lrbox} 1086 \newbox\myboxB 1087 \begin{lrbox}{\myboxB} 1088 \begin{python}[aboveskip=0pt,belowskip=0pt] 1089 1090 def Fibonacci(): 1091 fn = 0; fn1 = fn; `yield fn` # suspend 1092 fn = 1; fn2 = fn1; fn1 = fn; `yield fn` 1093 while True: 1094 fn = fn1 + fn2; fn2 = fn1; fn1 = fn; `yield fn` 1095 1096 1097 f1 = Fibonacci() 1098 f2 = Fibonacci() 1099 for i in range( 10 ): 1100 print( `next( f1 )`, `next( f2 )` ) # resume 1101 1102 \end{python} 1103 \end{lrbox} 1104 \subfloat[\CFA]{\label{f:Coroutine3States}\usebox\myboxA} 1105 \qquad 1106 \subfloat[Python]{\label{f:Coroutine1State}\usebox\myboxB} 1107 \caption{Fibonacci input coroutine, 3 states, internal variables} 1108 \label{f:cfa-fibonacci} 1109 \end{figure} 1110 \end{comment} 845 The previous examples are \newterm{asymmetric (semi) coroutine}s because one coroutine always calls a resuming routine for another coroutine, and the resumed coroutine always suspends back to its last resumer, similar to call/return for normal routines 846 However, there is no stack growth because @resume@/@suspend@ context switch to existing stack-frames rather than create new ones. 847 \newterm{Symmetric (full) coroutine}s have a coroutine call a resuming routine for another coroutine, which eventually forms a resuming-call cycle. 848 (The trivial cycle is a coroutine resuming itself.) 849 This control flow is similar to recursion for normal routines, but again there is no stack growth from the context switch. 1111 850 1112 851 \begin{figure} … … 1116 855 \begin{cfa} 1117 856 `coroutine` Prod { 1118 Cons & c; // communication857 Cons & c; 1119 858 int N, money, receipt; 1120 859 }; 1121 860 void main( Prod & prod ) with( prod ) { 1122 861 // 1st resume starts here 1123 for ( i ; N) {862 for ( int i = 0; i < N; i += 1 ) { 1124 863 int p1 = random( 100 ), p2 = random( 100 ); 1125 sout | p1 | " " | p2 ;864 sout | p1 | " " | p2 | endl; 1126 865 int status = delivery( c, p1, p2 ); 1127 sout | " $" | money | nl | status;866 sout | " $" | money | endl | status | endl; 1128 867 receipt += 1; 1129 868 } 1130 869 stop( c ); 1131 sout | "prod stops" ;870 sout | "prod stops" | endl; 1132 871 } 1133 872 int payment( Prod & prod, int money ) { … … 1150 889 \begin{cfa} 1151 890 `coroutine` Cons { 1152 Prod & p; // communication891 Prod & p; 1153 892 int p1, p2, status; 1154 bool done;893 _Bool done; 1155 894 }; 1156 895 void ?{}( Cons & cons, Prod & p ) { 1157 &cons.p = &p; // reassignable reference896 &cons.p = &p; 1158 897 cons.[status, done ] = [0, false]; 1159 898 } 899 void ^?{}( Cons & cons ) {} 1160 900 void main( Cons & cons ) with( cons ) { 1161 901 // 1st resume starts here 1162 902 int money = 1, receipt; 1163 903 for ( ; ! done; ) { 1164 sout | p1 | " " | p2 | nl | " $" | money;904 sout | p1 | " " | p2 | endl | " $" | money | endl; 1165 905 status += 1; 1166 906 receipt = payment( p, money ); 1167 sout | " #" | receipt ;907 sout | " #" | receipt | endl; 1168 908 money += 1; 1169 909 } 1170 sout | "cons stops" ;910 sout | "cons stops" | endl; 1171 911 } 1172 912 int delivery( Cons & cons, int p1, int p2 ) { … … 1179 919 `resume( cons );` 1180 920 } 1181 1182 921 \end{cfa} 1183 922 \end{tabular} 1184 \caption{Producer / consumer: resume-resume cycle, bi directional communication}923 \caption{Producer / consumer: resume-resume cycle, bi-directional communication} 1185 924 \label{f:ProdCons} 1186 925 \end{figure} 1187 926 1188 Figure~\ref{f:ProdCons} shows the ping-pong example in Figure~\ref{f:CFAPingPongGen} extended into a producer/consumer symmetric-coroutine performing bidirectional communication.1189 This example is illustrative because both producer/consumer have two interface functions with @resume@s that suspend execution in these interface (helper) functions.1190 The program main creates the producer coroutine, passes it to the consumer coroutine in its initialization, and closes the cycle at the call to @start@ along with the number of items to be produced.1191 The first @resume@ of@prod@ creates @prod@'s stack with a frame for @prod@'s coroutine main at the top, and context switches to it.1192 @prod@'s coroutine main starts, creates local -statevariables that are retained between coroutine activations, and executes $N$ iterations, each generating two random values, calling the consumer to deliver the values, and printing the status returned from the consumer.927 Figure~\ref{f:ProdCons} shows a producer/consumer symmetric-coroutine performing bi-directional communication. 928 Since the solution involves a full-coroutining cycle, the program main creates one coroutine in isolation, passes this coroutine to its partner, and closes the cycle at the call to @start@. 929 The @start@ routine communicates both the number of elements to be produced and the consumer into the producer's coroutine structure. 930 Then the @resume@ to @prod@ creates @prod@'s stack with a frame for @prod@'s coroutine main at the top, and context switches to it. 931 @prod@'s coroutine main starts, creates local variables that are retained between coroutine activations, and executes $N$ iterations, each generating two random values, calling the consumer to deliver the values, and printing the status returned from the consumer. 1193 932 1194 933 The producer call to @delivery@ transfers values into the consumer's communication variables, resumes the consumer, and returns the consumer status. 1195 On the first resume, @cons@'s stack is created and initialized, holding local-statevariables retained between subsequent activations of the coroutine.1196 The consumer iterates until the @done@ flag is set, prints the values delivered by the producer, increments status, and calls back to the producer via @payment@, and on return from @payment@, prints the receipt from the producer and increments @money@ (inflation).1197 The call from the consumer to @payment@ introduces the cycle between producer and consumer.934 For the first resume, @cons@'s stack is initialized, creating local variables retained between subsequent activations of the coroutine. 935 The consumer iterates until the @done@ flag is set, prints, increments status, and calls back to the producer via @payment@, and on return from @payment@, prints the receipt from the producer and increments @money@ (inflation). 936 The call from the consumer to the @payment@ introduces the cycle between producer and consumer. 1198 937 When @payment@ is called, the consumer copies values into the producer's communication variable and a resume is executed. 1199 The context switch restarts the producer at the point where it last context switched, so it continues in @delivery@ after the resume. 938 The context switch restarts the producer at the point where it was last context switched, so it continues in @delivery@ after the resume. 939 1200 940 @delivery@ returns the status value in @prod@'s coroutine main, where the status is printed. 1201 941 The loop then repeats calling @delivery@, where each call resumes the consumer coroutine. … … 1203 943 The consumer increments and returns the receipt to the call in @cons@'s coroutine main. 1204 944 The loop then repeats calling @payment@, where each call resumes the producer coroutine. 1205 Figure~\ref{f:ProdConsRuntimeStacks} shows the runtime stacks of the program main, and the coroutine mains for @prod@ and @cons@ during the cycling. 1206 1207 \begin{figure} 1208 \begin{center} 1209 \input{FullProdConsStack.pstex_t} 1210 \end{center} 1211 \vspace*{-10pt} 1212 \caption{Producer / consumer runtime stacks} 1213 \label{f:ProdConsRuntimeStacks} 1214 1215 \medskip 1216 1217 \begin{center} 1218 \input{FullCoroutinePhases.pstex_t} 1219 \end{center} 1220 \vspace*{-10pt} 1221 \caption{Ping / Pong coroutine steps} 1222 \label{f:PingPongFullCoroutineSteps} 1223 \end{figure} 1224 1225 Terminating a coroutine cycle is more complex than a generator cycle, because it requires context switching to the program main's \emph{stack} to shutdown the program, whereas generators started by the program main run on its stack. 1226 Furthermore, each deallocated coroutine must guarantee all destructors are run for object allocated in the coroutine type \emph{and} allocated on the coroutine's stack at the point of suspension, which can be arbitrarily deep. 1227 When a coroutine's main ends, its stack is already unwound so any stack allocated objects with destructors have been finalized. 1228 The na\"{i}ve semantics for coroutine-cycle termination is to context switch to the last resumer, like executing a @suspend@/@return@ in a generator. 1229 However, for coroutines, the last resumer is \emph{not} implicitly below the current stack frame, as for generators, because each coroutine's stack is independent. 1230 Unfortunately, it is impossible to determine statically if a coroutine is in a cycle and unrealistic to check dynamically (graph-cycle problem). 1231 Hence, a compromise solution is necessary that works for asymmetric (acyclic) and symmetric (cyclic) coroutines. 1232 1233 Our solution is to context switch back to the first resumer (starter) once the coroutine ends. 1234 This semantics works well for the most common asymmetric and symmetric coroutine usage patterns. 1235 For asymmetric coroutines, it is common for the first resumer (starter) coroutine to be the only resumer. 1236 All previous generators converted to coroutines have this property. 1237 For symmetric coroutines, it is common for the cycle creator to persist for the lifetime of the cycle. 1238 Hence, the starter coroutine is remembered on the first resume and ending the coroutine resumes the starter. 1239 Figure~\ref{f:ProdConsRuntimeStacks} shows this semantic by the dashed lines from the end of the coroutine mains: @prod@ starts @cons@ so @cons@ resumes @prod@ at the end, and the program main starts @prod@ so @prod@ resumes the program main at the end. 1240 For other scenarios, it is always possible to devise a solution with additional programming effort, such as forcing the cycle forward (backward) to a safe point before starting termination. 1241 1242 The producer/consumer example does not illustrate the full power of the starter semantics because @cons@ always ends first. 1243 Assume generator @PingPong@ is converted to a coroutine. 1244 Figure~\ref{f:PingPongFullCoroutineSteps} shows the creation, starter, and cyclic execution steps of the coroutine version. 1245 The program main creates (declares) coroutine instances @ping@ and @pong@. 1246 Next, program main resumes @ping@, making it @ping@'s starter, and @ping@'s main resumes @pong@'s main, making it @pong@'s starter. 1247 Execution forms a cycle when @pong@ resumes @ping@, and cycles $N$ times. 1248 By adjusting $N$ for either @ping@/@pong@, it is possible to have either one finish first, instead of @pong@ always ending first. 1249 If @pong@ ends first, it resumes its starter @ping@ in its coroutine main, then @ping@ ends and resumes its starter the program main in function @start@. 1250 If @ping@ ends first, it resumes its starter the program main in function @start@. 1251 Regardless of the cycle complexity, the starter stack always leads back to the program main, but the stack can be entered at an arbitrary point. 1252 Once back at the program main, coroutines @ping@ and @pong@ are deallocated. 1253 For generators, deallocation runs the destructors for all objects in the generator type. 1254 For coroutines, deallocation deals with objects in the coroutine type and must also run the destructors for any objects pending on the coroutine's stack for any unterminated coroutine. 1255 Hence, if a coroutine's destructor detects the coroutine is not ended, it implicitly raises a cancellation exception (uncatchable exception) at the coroutine and resumes it so the cancellation exception can propagate to the root of the coroutine's stack destroying all local variable on the stack. 1256 So the \CFA semantics for the generator and coroutine, ensure both can be safely deallocated at any time, regardless of their current state, like any other aggregate object. 1257 Explicitly raising normal exceptions at another coroutine can replace flag variables, like @stop@, \eg @prod@ raises a @stop@ exception at @cons@ after it finishes generating values and resumes @cons@, which catches the @stop@ exception to terminate its loop. 1258 1259 Finally, there is an interesting effect for @suspend@ with symmetric coroutines. 1260 A coroutine must retain its last resumer to suspend back because the resumer is on a different stack. 1261 These reverse pointers allow @suspend@ to cycle \emph{backwards}, which may be useful in certain cases. 1262 However, there is an anomaly if a coroutine resumes itself, because it overwrites its last resumer with itself, losing the ability to resume the last external resumer. 1263 To prevent losing this information, a self-resume does not overwrite the last resumer. 1264 1265 1266 \subsection{Generator / Coroutine Implementation} 1267 1268 A significant implementation challenge for generators/coroutines (and threads in Section~\ref{s:threads}) is adding extra fields to the custom types and related functions, \eg inserting code after/before the coroutine constructor/destructor and @main@ to create/initialize/de-initialize/destroy any extra fields, \eg stack. 1269 There are several solutions to these problem, which follow from the object-oriented flavour of adopting custom types. 1270 1271 For object-oriented languages, inheritance is used to provide extra fields and code via explicit inheritance: 1272 \begin{cfa}[morekeywords={class,inherits}] 1273 class myCoroutine inherits baseCoroutine { ... } 1274 \end{cfa} 1275 % The problem is that the programming language and its tool chain, \eg debugger, @valgrind@, need to understand @baseCoroutine@ because it infers special property, so type @baseCoroutine@ becomes a de facto keyword and all types inheriting from it are implicitly custom types. 1276 The problem is that some special properties are not handled by existing language semantics, \eg the execution of constructors/destructors is in the wrong order to implicitly start threads because the thread must start \emph{after} all constructors as it relies on a completely initialized object, but the inherited constructor runs \emph{before} the derived. 1277 Alternatives, such as explicitly starting threads as in Java, are repetitive and forgetting to call start is a common source of errors. 1278 An alternative is composition: 1279 \begin{cfa} 1280 struct myCoroutine { 1281 ... // declaration/communication variables 945 946 After iterating $N$ times, the producer calls @stop@. 947 The @done@ flag is set to stop the consumer's execution and a resume is executed. 948 The context switch restarts @cons@ in @payment@ and it returns with the last receipt. 949 The consumer terminates its loops because @done@ is true, its @main@ terminates, so @cons@ transitions from a coroutine back to an object, and @prod@ reactivates after the resume in @stop@. 950 @stop@ returns and @prod@'s coroutine main terminates. 951 The program main restarts after the resume in @start@. 952 @start@ returns and the program main terminates. 953 954 955 \subsection{Coroutine Implementation} 956 957 A significant implementation challenge for coroutines (and threads, see section \ref{threads}) is adding extra fields and executing code after/before the coroutine constructor/destructor and coroutine main to create/initialize/de-initialize/destroy extra fields and the stack. 958 There are several solutions to this problem and the chosen option forced the \CFA coroutine design. 959 960 Object-oriented inheritance provides extra fields and code in a restricted context, but it requires programmers to explicitly perform the inheritance: 961 \begin{cfa} 962 struct mycoroutine $\textbf{\textsf{inherits}}$ baseCoroutine { ... } 963 \end{cfa} 964 and the programming language (and possibly its tool set, \eg debugger) may need to understand @baseCoroutine@ because of the stack. 965 Furthermore, the execution of constructs/destructors is in the wrong order for certain operations, \eg for threads; 966 \eg, if the thread is implicitly started, it must start \emph{after} all constructors, because the thread relies on a completely initialized object, but the inherited constructor runs \emph{before} the derived. 967 968 An alternatively is composition: 969 \begin{cfa} 970 struct mycoroutine { 971 ... // declarations 1282 972 baseCoroutine dummy; // composition, last declaration 1283 973 } 1284 974 \end{cfa} 1285 which also requires an explicit declaration that must be lastto ensure correct initialization order.975 which also requires an explicit declaration that must be the last one to ensure correct initialization order. 1286 976 However, there is nothing preventing wrong placement or multiple declarations. 1287 977 1288 \CFA custom types make any special properties explicit to the language and its tool chain, \eg the language code-generator knows where to inject code 1289 % and when it is unsafe to perform certain optimizations, 1290 and IDEs using simple parsing can find and manipulate types with special properties. 1291 The downside of this approach is that it makes custom types a special case in the language. 1292 Users wanting to extend custom types or build their own can only do so in ways offered by the language. 1293 Furthermore, implementing custom types without language support may display the power of a programming language. 1294 \CFA blends the two approaches, providing custom type for idiomatic \CFA code, while extending and building new custom types is still possible, similar to Java concurrency with builtin and library. 1295 1296 Part of the mechanism to generalize custom types is the \CFA trait~\cite[\S~2.3]{Moss18}, \eg the definition for custom-type @coroutine@ is anything satisfying the trait @is_coroutine@, and this trait both enforces and restricts the coroutine-interface functions. 978 For coroutines as for threads, many implementations are based on routine pointers or routine objects~\cite{Butenhof97, C++14, MS:VisualC++, BoostCoroutines15}. 979 For example, Boost implements coroutines in terms of four functor object-types: 980 \begin{cfa} 981 asymmetric_coroutine<>::pull_type 982 asymmetric_coroutine<>::push_type 983 symmetric_coroutine<>::call_type 984 symmetric_coroutine<>::yield_type 985 \end{cfa} 986 Similarly, the canonical threading paradigm is often based on routine pointers, \eg @pthread@~\cite{pthreads}, \Csharp~\cite{Csharp}, Go~\cite{Go}, and Scala~\cite{Scala}. 987 However, the generic thread-handle (identifier) is limited (few operations), unless it is wrapped in a custom type. 988 \begin{cfa} 989 void mycor( coroutine_t cid, void * arg ) { 990 int * value = (int *)arg; $\C{// type unsafe, pointer-size only}$ 991 // Coroutine body 992 } 993 int main() { 994 int input = 0, output; 995 coroutine_t cid = coroutine_create( &mycor, (void *)&input ); $\C{// type unsafe, pointer-size only}$ 996 coroutine_resume( cid, (void *)input, (void **)&output ); $\C{// type unsafe, pointer-size only}$ 997 } 998 \end{cfa} 999 Since the custom type is simple to write in \CFA and solves several issues, added support for routine/lambda-based coroutines adds very little. 1000 1001 Note, the type @coroutine_t@ must be an abstract handle to the coroutine, because the coroutine descriptor and its stack are non-copyable. 1002 Copying the coroutine descriptor results in copies being out of date with the current state of the stack. 1003 Correspondingly, copying the stack results is copies being out of date with coroutine descriptor, and pointers in the stack being out of date to data on the stack. 1004 (There is no mechanism in C to find all stack-specific pointers and update them as part of a copy.) 1005 1006 The selected approach is to use language support by introducing a new kind of aggregate (structure): 1007 \begin{cfa} 1008 coroutine Fibonacci { 1009 int fn; // communication variables 1010 }; 1011 \end{cfa} 1012 The @coroutine@ keyword means the compiler (and tool set) can find and inject code where needed. 1013 The downside of this approach is that it makes coroutine a special case in the language. 1014 Users wanting to extend coroutines or build their own for various reasons can only do so in ways offered by the language. 1015 Furthermore, implementing coroutines without language supports also displays the power of a programming language. 1016 While this is ultimately the option used for idiomatic \CFA code, coroutines and threads can still be constructed without using the language support. 1017 The reserved keyword eases use for the common cases. 1018 1019 Part of the mechanism to generalize coroutines is using a \CFA trait, which defines a coroutine as anything satisfying the trait @is_coroutine@, and this trait is used to restrict coroutine-manipulation routines: 1297 1020 \begin{cfa} 1298 1021 trait is_coroutine( `dtype` T ) { … … 1300 1023 coroutine_desc * get_coroutine( T & ); 1301 1024 }; 1302 forall( `dtype` T | is_coroutine(T) ) void $suspend$( T & ), resume( T & ); 1303 \end{cfa} 1304 Note, copying generators/coroutines/threads is not meaningful. 1305 For example, both the resumer and suspender descriptors can have bidirectional pointers; 1306 copying these coroutines does not update the internal pointers so behaviour of both copies would be difficult to understand. 1307 Furthermore, two coroutines cannot logically execute on the same stack. 1308 A deep coroutine copy, which copies the stack, is also meaningless in an unmanaged language (no garbage collection), like C, because the stack may contain pointers to object within it that require updating for the copy. 1309 The \CFA @dtype@ property provides no \emph{implicit} copying operations and the @is_coroutine@ trait provides no \emph{explicit} copying operations, so all coroutines must be passed by reference (pointer). 1310 The function definitions ensure there is a statically typed @main@ function that is the starting point (first stack frame) of a coroutine, and a mechanism to get (read) the coroutine descriptor from its handle. 1311 The @main@ function has no return value or additional parameters because the coroutine type allows an arbitrary number of interface functions with corresponding arbitrary typed input/output values versus fixed ones. 1312 The advantage of this approach is that users can easily create different types of coroutines, \eg changing the memory layout of a coroutine is trivial when implementing the @get_coroutine@ function, and possibly redefining \textsf{suspend} and @resume@. 1313 1314 The \CFA custom-type @coroutine@ implicitly implements the getter and forward declarations for the coroutine main. 1025 forall( `dtype` T | is_coroutine(T) ) void suspend( T & ); 1026 forall( `dtype` T | is_coroutine(T) ) void resume( T & ); 1027 \end{cfa} 1028 The @dtype@ property of the trait ensures the coroutine descriptor is non-copyable, so all coroutines must be passed by reference (pointer). 1029 The routine definitions ensures there is a statically-typed @main@ routine that is the starting point (first stack frame) of a coroutine, and a mechanism to get (read) the currently executing coroutine handle. 1030 The @main@ routine has no return value or additional parameters because the coroutine type allows an arbitrary number of interface routines with corresponding arbitrary typed input/output values versus fixed ones. 1031 The generic routines @suspend@ and @resume@ can be redefined, but any object passed to them is a coroutine since it must satisfy the @is_coroutine@ trait to compile. 1032 The advantage of this approach is that users can easily create different types of coroutines, for example, changing the memory layout of a coroutine is trivial when implementing the @get_coroutine@ routine, and possibly redefining @suspend@ and @resume@. 1033 The \CFA keyword @coroutine@ implicitly implements the getter and forward declarations required for implementing the coroutine main: 1315 1034 \begin{cquote} 1316 1035 \begin{tabular}{@{}ccc@{}} … … 1348 1067 \end{tabular} 1349 1068 \end{cquote} 1350 The combination of custom types and fundamental @trait@ description of these types allows a concise specification for programmers and tools, while more advanced programmers can have tighter control over memory layout and initialization. 1351 1352 Figure~\ref{f:CoroutineMemoryLayout} shows different memory-layout options for a coroutine (where a task is similar). 1353 The coroutine handle is the @coroutine@ instance containing programmer specified type global/communication variables across interface functions. 1354 The coroutine descriptor contains all implicit declarations needed by the runtime, \eg @suspend@/@resume@, and can be part of the coroutine handle or separate. 1355 The coroutine stack can appear in a number of locations and be fixed or variable sized. 1356 Hence, the coroutine's stack could be a VLS\footnote{ 1357 We are examining variable-sized structures (VLS), where fields can be variable-sized structures or arrays. 1358 Once allocated, a VLS is fixed sized.} 1359 on the allocating stack, provided the allocating stack is large enough. 1360 For a VLS stack allocation/deallocation is an inexpensive adjustment of the stack pointer, modulo any stack constructor costs (\eg initial frame setup). 1361 For heap stack allocation, allocation/deallocation is an expensive heap allocation (where the heap can be a shared resource), modulo any stack constructor costs. 1362 With heap stack allocation, it is also possible to use a split (segmented) stack calling convention, available with gcc and clang, so the stack is variable sized. 1363 Currently, \CFA supports stack/heap allocated descriptors but only fixed-sized heap allocated stacks. 1364 In \CFA debug-mode, the fixed-sized stack is terminated with a write-only page, which catches most stack overflows. 1365 Experience teaching concurrency with \uC~\cite{CS343} shows fixed-sized stacks are rarely an issue for students. 1366 Split-stack allocation is under development but requires recompilation of legacy code, which may be impossible. 1367 1368 \begin{figure} 1369 \centering 1370 \input{corlayout.pstex_t} 1371 \caption{Coroutine memory layout} 1372 \label{f:CoroutineMemoryLayout} 1373 \end{figure} 1374 1375 1376 \section{Concurrency} 1377 \label{s:Concurrency} 1378 1379 Concurrency is nondeterministic scheduling of independent sequential execution paths (threads), where each thread has its own stack. 1380 A single thread with multiple call stacks, \newterm{coroutining}~\cite{Conway63,Marlin80}, does \emph{not} imply concurrency~\cite[\S~2]{Buhr05a}. 1381 In coroutining, coroutines self-schedule the thread across stacks so execution is deterministic. 1382 (It is \emph{impossible} to generate a concurrency error when coroutining.) 1383 However, coroutines are a stepping stone towards concurrency. 1384 1385 The transition to concurrency, even for a single thread with multiple stacks, occurs when coroutines context switch to a \newterm{scheduling coroutine}, introducing non-determinism from the coroutine perspective~\cite[\S~3,]{Buhr05a}. 1386 Therefore, a minimal concurrency system requires coroutines \emph{in conjunction with a nondeterministic scheduler}. 1387 The resulting execution system now follows a cooperative threading model~\cite{Adya02,libdill}, called \newterm{non-preemptive scheduling}. 1388 Adding \newterm{preemption} introduces non-cooperative scheduling, where context switching occurs randomly between any two instructions often based on a timer interrupt, called \newterm{preemptive scheduling}. 1389 While a scheduler introduces uncertain execution among explicit context switches, preemption introduces uncertainty by introducing implicit context switches. 1390 Uncertainty gives the illusion of parallelism on a single processor and provides a mechanism to access and increase performance on multiple processors. 1391 The reason is that the scheduler/runtime have complete knowledge about resources and how to best utilized them. 1392 However, the introduction of unrestricted nondeterminism results in the need for \newterm{mutual exclusion} and \newterm{synchronization}, which restrict nondeterminism for correctness; 1393 otherwise, it is impossible to write meaningful concurrent programs. 1394 Optimal concurrent performance is often obtained by having as much nondeterminism as mutual exclusion and synchronization correctness allow. 1395 1396 A scheduler can either be a stackless or stackful. 1397 For stackless, the scheduler performs scheduling on the stack of the current coroutine and switches directly to the next coroutine, so there is one context switch. 1398 For stackful, the current coroutine switches to the scheduler, which performs scheduling, and it then switches to the next coroutine, so there are two context switches. 1399 The \CFA runtime uses a stackful scheduler for uniformity and security. 1400 1401 1402 \subsection{Thread} 1403 \label{s:threads} 1404 1405 Threading needs the ability to start a thread and wait for its completion. 1406 A common API for this ability is @fork@ and @join@. 1069 The combination of these two approaches allows an easy and concise specification to coroutining (and concurrency) for normal users, while more advanced users have tighter control on memory layout and initialization. 1070 1071 1072 \subsection{Thread Interface} 1073 \label{threads} 1074 1075 Both user and kernel threads are supported, where user threads provide concurrency and kernel threads provide parallelism. 1076 Like coroutines and for the same design reasons, the selected approach for user threads is to use language support by introducing a new kind of aggregate (structure) and a \CFA trait: 1407 1077 \begin{cquote} 1408 \begin{tabular}{@{}lll@{}} 1409 \multicolumn{1}{c}{\textbf{Java}} & \multicolumn{1}{c}{\textbf{\Celeven}} & \multicolumn{1}{c}{\textbf{pthreads}} \\ 1410 \begin{cfa} 1411 class MyTask extends Thread {...} 1412 mytask t = new MyTask(...); 1413 `t.start();` // start 1414 // concurrency 1415 `t.join();` // wait 1078 \begin{tabular}{@{}c@{\hspace{3\parindentlnth}}c@{}} 1079 \begin{cfa} 1080 thread myThread { 1081 // communication variables 1082 }; 1083 1084 1416 1085 \end{cfa} 1417 1086 & 1418 1087 \begin{cfa} 1419 class MyTask { ... } // functor 1420 MyTask mytask; 1421 `thread t( mytask, ... );` // start 1422 // concurrency 1423 `t.join();` // wait 1424 \end{cfa} 1425 & 1426 \begin{cfa} 1427 void * rtn( void * arg ) {...} 1428 pthread_t t; int i = 3; 1429 `pthread_create( &t, rtn, (void *)i );` // start 1430 // concurrency 1431 `pthread_join( t, NULL );` // wait 1088 trait is_thread( `dtype` T ) { 1089 void main( T & ); 1090 thread_desc * get_thread( T & ); 1091 void ^?{}( T & `mutex` ); 1092 }; 1432 1093 \end{cfa} 1433 1094 \end{tabular} 1434 1095 \end{cquote} 1435 \CFA has a simpler approach using a custom @thread@ type and leveraging declaration semantics (allocation/deallocation), where threads implicitly @fork@ after construction and @join@ before destruction. 1436 \begin{cfa} 1437 thread MyTask {}; 1438 void main( MyTask & this ) { ... } 1096 (The qualifier @mutex@ for the destructor parameter is discussed in Section~\ref{s:Monitors}.) 1097 Like a coroutine, the statically-typed @main@ routine is the starting point (first stack frame) of a user thread. 1098 The difference is that a coroutine borrows a thread from its caller, so the first thread resuming a coroutine creates an instance of @main@; 1099 whereas, a user thread receives its own thread from the runtime system, which starts in @main@ as some point after the thread constructor is run.\footnote{ 1100 The \lstinline@main@ routine is already a special routine in C (where the program begins), so it is a natural extension of the semantics to use overloading to declare mains for different coroutines/threads (the normal main being the main of the initial thread).} 1101 No return value or additional parameters are necessary for this routine because the task type allows an arbitrary number of interface routines with corresponding arbitrary typed input/output values. 1102 1103 \begin{comment} % put in appendix with coroutine version ??? 1104 As such the @main@ routine of a thread can be defined as 1105 \begin{cfa} 1106 thread foo {}; 1107 1108 void main(foo & this) { 1109 sout | "Hello World!" | endl; 1110 } 1111 \end{cfa} 1112 1113 In this example, threads of type @foo@ start execution in the @void main(foo &)@ routine, which prints @"Hello World!".@ While this paper encourages this approach to enforce strongly typed programming, users may prefer to use the routine-based thread semantics for the sake of simplicity. 1114 With the static semantics it is trivial to write a thread type that takes a routine pointer as a parameter and executes it on its stack asynchronously. 1115 \begin{cfa} 1116 typedef void (*voidRtn)(int); 1117 1118 thread RtnRunner { 1119 voidRtn func; 1120 int arg; 1121 }; 1122 1123 void ?{}(RtnRunner & this, voidRtn inRtn, int arg) { 1124 this.func = inRtn; 1125 this.arg = arg; 1126 } 1127 1128 void main(RtnRunner & this) { 1129 // thread starts here and runs the routine 1130 this.func( this.arg ); 1131 } 1132 1133 void hello(/*unused*/ int) { 1134 sout | "Hello World!" | endl; 1135 } 1136 1439 1137 int main() { 1440 MyTask team`[10]`; $\C[2.5in]{// allocate stack-based threads, implicit start after construction}$ 1441 // concurrency 1442 } $\C{// deallocate stack-based threads, implicit joins before destruction}$ 1443 \end{cfa} 1444 This semantic ensures a thread is started and stopped exactly once, eliminating some programming error, and scales to multiple threads for basic (termination) synchronization. 1445 For block allocation to arbitrary depth, including recursion, threads are created/destroyed in a lattice structure (tree with top and bottom). 1446 Arbitrary topologies are possible using dynamic allocation, allowing threads to outlive their declaration scope, identical to normal dynamic allocation. 1447 \begin{cfa} 1448 MyTask * factory( int N ) { ... return `anew( N )`; } $\C{// allocate heap-based threads, implicit start after construction}$ 1138 RtnRunner f = {hello, 42}; 1139 return 0? 1140 } 1141 \end{cfa} 1142 A consequence of the strongly typed approach to main is that memory layout of parameters and return values to/from a thread are now explicitly specified in the \textbf{api}. 1143 \end{comment} 1144 1145 For user threads to be useful, it must be possible to start and stop the underlying thread, and wait for it to complete execution. 1146 While using an API such as @fork@ and @join@ is relatively common, such an interface is awkward and unnecessary. 1147 A simple approach is to use allocation/deallocation principles, and have threads implicitly @fork@ after construction and @join@ before destruction. 1148 \begin{cfa} 1149 thread World {}; 1150 void main( World & this ) { 1151 sout | "World!" | endl; 1152 } 1449 1153 int main() { 1450 MyTask * team = factory( 10 ); 1451 // concurrency 1452 `delete( team );` $\C{// deallocate heap-based threads, implicit joins before destruction}\CRT$ 1453 } 1454 \end{cfa} 1455 1456 Figure~\ref{s:ConcurrentMatrixSummation} shows concurrently adding the rows of a matrix and then totalling the subtotals sequentially, after all the row threads have terminated. 1154 World w`[10]`; $\C{// implicit forks after creation}$ 1155 sout | "Hello " | endl; $\C{// "Hello " and 10 "World!" printed concurrently}$ 1156 } $\C{// implicit joins before destruction}$ 1157 \end{cfa} 1158 This semantics ensures a thread is started and stopped exactly once, eliminating some programming error, and scales to multiple threads for basic (termination) synchronization. 1159 This tree-structure (lattice) create/delete from C block-structure is generalized by using dynamic allocation, so threads can outlive the scope in which they are created, much like dynamically allocating memory lets objects outlive the scope in which they are created. 1160 \begin{cfa} 1161 int main() { 1162 MyThread * heapLived; 1163 { 1164 MyThread blockLived; $\C{// fork block-based thread}$ 1165 heapLived = `new`( MyThread ); $\C{// fork heap-based thread}$ 1166 ... 1167 } $\C{// join block-based thread}$ 1168 ... 1169 `delete`( heapLived ); $\C{// join heap-based thread}$ 1170 } 1171 \end{cfa} 1172 The heap-based approach allows arbitrary thread-creation topologies, with respect to fork/join-style concurrency. 1173 1174 Figure~\ref{s:ConcurrentMatrixSummation} shows concurrently adding the rows of a matrix and then totalling the subtotals sequential, after all the row threads have terminated. 1457 1175 The program uses heap-based threads because each thread needs different constructor values. 1458 1176 (Python provides a simple iteration mechanism to initialize array elements to different values allowing stack allocation.) 1459 The allocation/deallocation pattern appears unusual because allocated objects are immediately de allocated without any intervening code.1177 The allocation/deallocation pattern appears unusual because allocated objects are immediately deleted without any intervening code. 1460 1178 However, for threads, the deletion provides implicit synchronization, which is the intervening code. 1461 % While the subtotals are added in linear order rather than completion order, which slightlyinhibits concurrency, the computation is restricted by the critical-path thread (\ie the thread that takes the longest), and so any inhibited concurrency is very small as totalling the subtotals is trivial.1179 While the subtotals are added in linear order rather than completion order, which slight inhibits concurrency, the computation is restricted by the critical-path thread (\ie the thread that takes the longest), and so any inhibited concurrency is very small as totalling the subtotals is trivial. 1462 1180 1463 1181 \begin{figure} 1464 1182 \begin{cfa} 1465 `thread` Adder { int * row, cols, & subtotal; } $\C{// communication variables}$ 1183 thread Adder { 1184 int * row, cols, & subtotal; $\C{// communication}$ 1185 }; 1466 1186 void ?{}( Adder & adder, int row[], int cols, int & subtotal ) { 1467 adder.[ row, cols, &subtotal ] = [ row, cols, &subtotal ];1187 adder.[ row, cols, &subtotal ] = [ row, cols, &subtotal ]; 1468 1188 } 1469 1189 void main( Adder & adder ) with( adder ) { 1470 subtotal = 0; 1471 for ( c; cols ) { subtotal += row[c]; } 1190 subtotal = 0; 1191 for ( int c = 0; c < cols; c += 1 ) { 1192 subtotal += row[c]; 1193 } 1472 1194 } 1473 1195 int main() { 1474 const int rows = 10, cols = 1000;1475 int matrix[rows][cols], subtotals[rows], total = 0;1476 // read matrix1477 Adder * adders[rows];1478 for ( r; rows; ) {$\C{// start threads to sum rows}$1479 adders[r] = `new( matrix[r], cols, &subtotals[r] );`1480 }1481 for ( r; rows ) {$\C{// wait for threads to finish}$1482 `delete( adders[r] );`$\C{// termination join}$1483 total += subtotals[r]; $\C{// total subtotal}$1484 }1485 sout | total;1486 } 1487 \end{cfa} 1488 \caption{Concurrent matrix summation}1196 const int rows = 10, cols = 1000; 1197 int matrix[rows][cols], subtotals[rows], total = 0; 1198 // read matrix 1199 Adder * adders[rows]; 1200 for ( int r = 0; r < rows; r += 1 ) { $\C{// start threads to sum rows}$ 1201 adders[r] = new( matrix[r], cols, &subtotals[r] ); 1202 } 1203 for ( int r = 0; r < rows; r += 1 ) { $\C{// wait for threads to finish}$ 1204 delete( adders[r] ); $\C{// termination join}$ 1205 total += subtotals[r]; $\C{// total subtotal}$ 1206 } 1207 sout | total | endl; 1208 } 1209 \end{cfa} 1210 \caption{Concurrent Matrix Summation} 1489 1211 \label{s:ConcurrentMatrixSummation} 1490 1212 \end{figure} 1491 1213 1492 1214 1493 \subsection{Thread Implementation}1494 1495 Threads in \CFA are user level run by runtime kernel threads (see Section~\ref{s:CFARuntimeStructure}), where user threads provide concurrency and kernel threads provide parallelism.1496 Like coroutines, and for the same design reasons, \CFA provides a custom @thread@ type and a @trait@ to enforce and restrict the task-interface functions.1497 \begin{cquote}1498 \begin{tabular}{@{}c@{\hspace{3\parindentlnth}}c@{}}1499 \begin{cfa}1500 thread myThread {1501 ... // declaration/communication variables1502 };1503 1504 1505 \end{cfa}1506 &1507 \begin{cfa}1508 trait is_thread( `dtype` T ) {1509 void main( T & );1510 thread_desc * get_thread( T & );1511 void ^?{}( T & `mutex` );1512 };1513 \end{cfa}1514 \end{tabular}1515 \end{cquote}1516 Like coroutines, the @dtype@ property prevents \emph{implicit} copy operations and the @is_thread@ trait provides no \emph{explicit} copy operations, so threads must be passed by reference (pointer).1517 Similarly, the function definitions ensure there is a statically typed @main@ function that is the thread starting point (first stack frame), a mechanism to get (read) the thread descriptor from its handle, and a special destructor to prevent deallocation while the thread is executing.1518 (The qualifier @mutex@ for the destructor parameter is discussed in Section~\ref{s:Monitor}.)1519 The difference between the coroutine and thread is that a coroutine borrows a thread from its caller, so the first thread resuming a coroutine creates the coroutine's stack and starts running the coroutine main on the stack;1520 whereas, a thread is scheduling for execution in @main@ immediately after its constructor is run.1521 No return value or additional parameters are necessary for this function because the @thread@ type allows an arbitrary number of interface functions with corresponding arbitrary typed input/output values.1522 1523 1524 1215 \section{Mutual Exclusion / Synchronization} 1525 \label{s:MutualExclusionSynchronization} 1526 1527 Unrestricted nondeterminism is meaningless as there is no way to know when the result is completed without synchronization.1528 To produce meaningful execution requires clawing back some determinism using mutual exclusion and synchronization, where mutual exclusion provides access control for threads using shared data, and synchronization is a timing relationship among threads~\cite[\S~4]{Buhr05a}.1529 Some concurrent systems eliminate mutable shared-state by switching to stateless communication like message passing~\cite{Thoth,Harmony,V-Kernel,MPI} (Erlang, MPI), channels~\cite{CSP} (CSP,Go), actors~\cite{Akka} (Akka, Scala), or functional techniques (Haskell).1530 However, these approaches introduce a new communication mechanism for concurrency different from the standard communication using function call/return.1531 Hence, a programmer must learn and manipulate two sets of design /programmingpatterns.1216 1217 Uncontrolled non-deterministic execution is meaningless. 1218 To reestablish meaningful execution requires mechanisms to reintroduce determinism (\ie restrict non-determinism), called mutual exclusion and synchronization, where mutual exclusion is an access-control mechanism on data shared by threads, and synchronization is a timing relationship among threads~\cite[\S~4]{Buhr05a}. 1219 Since many deterministic challenges appear with the use of mutable shared state, some languages/libraries disallow it, \eg Erlang~\cite{Erlang}, Haskell~\cite{Haskell}, Akka~\cite{Akka} (Scala). 1220 In these paradigms, interaction among concurrent objects is performed by stateless message-passing~\cite{Thoth,Harmony,V-Kernel} or other paradigms closely relate to networking concepts (\eg channels~\cite{CSP,Go}). 1221 However, in call/return-based languages, these approaches force a clear distinction (\ie introduce a new programming paradigm) between regular and concurrent computation (\ie routine call versus message passing). 1222 Hence, a programmer must learn and manipulate two sets of design patterns. 1532 1223 While this distinction can be hidden away in library code, effective use of the library still has to take both paradigms into account. 1533 In contrast, approaches based on stateful models more closely resemble the standard call/return programmingmodel, resulting in a single programming paradigm.1534 1535 At the lowest level, concurrent control is implemented by atomic operations, upon which different kinds of lock ing mechanismsare constructed, \eg semaphores~\cite{Dijkstra68b}, barriers, and path expressions~\cite{Campbell74}.1224 In contrast, approaches based on statefull models more closely resemble the standard call/return programming-model, resulting in a single programming paradigm. 1225 1226 At the lowest level, concurrent control is implemented by atomic operations, upon which different kinds of locks mechanism are constructed, \eg semaphores~\cite{Dijkstra68b}, barriers, and path expressions~\cite{Campbell74}. 1536 1227 However, for productivity it is always desirable to use the highest-level construct that provides the necessary efficiency~\cite{Hochstein05}. 1537 1228 A newer approach for restricting non-determinism is transactional memory~\cite{Herlihy93}. 1538 While this approach is pursued in hardware~\cite{Nakaike15} and system languages, like \CC~\cite{Cpp-Transactions}, the performance and feature set is still too restrictive to be the main concurrency paradigm for system languages, which is why it is rejected as the core paradigm for concurrency in \CFA.1229 While this approach is pursued in hardware~\cite{Nakaike15} and system languages, like \CC~\cite{Cpp-Transactions}, the performance and feature set is still too restrictive to be the main concurrency paradigm for system languages, which is why it was rejected as the core paradigm for concurrency in \CFA. 1539 1230 1540 1231 One of the most natural, elegant, and efficient mechanisms for mutual exclusion and synchronization for shared-memory systems is the \emph{monitor}. 1541 First proposed by Brinch Hansen~\cite{Hansen73} and later described and extended by C.A.R.~Hoare~\cite{Hoare74}, many concurrent programming languages provide monitors as an explicit language construct: \eg Concurrent Pascal~\cite{ConcurrentPascal}, Mesa~\cite{Mesa}, Modula~\cite{Modula-2}, Turing~\cite{Turing:old}, Modula-3~\cite{Modula-3}, NeWS~\cite{NeWS}, Emerald~\cite{Emerald}, \uC~\cite{Buhr92a} and Java~\cite{Java}.1232 First proposed by Brinch Hansen~\cite{Hansen73} and later described and extended by C.A.R.~Hoare~\cite{Hoare74}, many concurrent programming-languages provide monitors as an explicit language construct: \eg Concurrent Pascal~\cite{ConcurrentPascal}, Mesa~\cite{Mesa}, Modula~\cite{Modula-2}, Turing~\cite{Turing:old}, Modula-3~\cite{Modula-3}, NeWS~\cite{NeWS}, Emerald~\cite{Emerald}, \uC~\cite{Buhr92a} and Java~\cite{Java}. 1542 1233 In addition, operating-system kernels and device drivers have a monitor-like structure, although they often use lower-level primitives such as mutex locks or semaphores to simulate monitors. 1543 For these reasons, \CFA selected monitors as the core high-level concurrency construct, upon which higher-level approaches can be easily constructed.1234 For these reasons, \CFA selected monitors as the core high-level concurrency-construct, upon which higher-level approaches can be easily constructed. 1544 1235 1545 1236 1546 1237 \subsection{Mutual Exclusion} 1547 1238 1548 A group of instructions manipulating a specific instance of shared data that must be performed atomically is called a \newterm{critical section}~\cite{Dijkstra65}, which is enforced by \newterm{simple mutual-exclusion}. 1549 The generalization is called a \newterm{group critical-section}~\cite{Joung00}, where multiple tasks with the same session use the resource simultaneously and different sessions are segregated, which is enforced by \newterm{complex mutual-exclusion} providing the correct kind and number of threads using a group critical-section. 1550 The readers/writer problem~\cite{Courtois71} is an instance of a group critical-section, where readers share a session but writers have a unique session. 1239 A group of instructions manipulating a specific instance of shared data that must be performed atomically is called an (individual) \newterm{critical-section}~\cite{Dijkstra65}. 1240 The generalization is called a \newterm{group critical-section}~\cite{Joung00}, where multiple tasks with the same session may use the resource simultaneously, but different sessions may not use the resource simultaneously. 1241 The readers/writer problem~\cite{Courtois71} is an instance of a group critical-section, where readers have the same session and all writers have a unique session. 1242 \newterm{Mutual exclusion} enforces that the correct kind and number of threads are using a critical section. 1551 1243 1552 1244 However, many solutions exist for mutual exclusion, which vary in terms of performance, flexibility and ease of use. 1553 1245 Methods range from low-level locks, which are fast and flexible but require significant attention for correctness, to higher-level concurrency techniques, which sacrifice some performance to improve ease of use. 1554 Ease of use comes by either guaranteeing some problems cannot occur , \eg deadlock free, or by offering a more explicit coupling between shared data and critical section.1555 For example, the \CC @std::atomic<T>@ offers an easy way to express mutual-exclusion on a restricted set of operations , \eg reading/writing,for numerical types.1246 Ease of use comes by either guaranteeing some problems cannot occur (\eg deadlock free), or by offering a more explicit coupling between shared data and critical section. 1247 For example, the \CC @std::atomic<T>@ offers an easy way to express mutual-exclusion on a restricted set of operations (\eg reading/writing) for numerical types. 1556 1248 However, a significant challenge with locks is composability because it takes careful organization for multiple locks to be used while preventing deadlock. 1557 1249 Easing composability is another feature higher-level mutual-exclusion mechanisms can offer. … … 1562 1254 Synchronization enforces relative ordering of execution, and synchronization tools provide numerous mechanisms to establish these timing relationships. 1563 1255 Low-level synchronization primitives offer good performance and flexibility at the cost of ease of use; 1564 higher-level mechanisms often simplify usage by adding better coupling between synchronization and data , \eg receive-specific versus receive-any thread in message passing or offering specialized solutions, \eg barrier lock.1565 Often synchronization is used to order access to a critical section, \eg ensuring a waiting writer thread enters the critical section before a calling reader thread.1566 If the calling reader is scheduled before the waiting writer, the reader has barged.1256 higher-level mechanisms often simplify usage by adding better coupling between synchronization and data (\eg message passing), or offering a simpler solution to otherwise involved challenges, \eg barrier lock. 1257 Often synchronization is used to order access to a critical section, \eg ensuring a reader thread is the next kind of thread to enter a critical section. 1258 If a writer thread is scheduled for next access, but another reader thread acquires the critical section first, that reader has \newterm{barged}. 1567 1259 Barging can result in staleness/freshness problems, where a reader barges ahead of a writer and reads temporally stale data, or a writer barges ahead of another writer overwriting data with a fresh value preventing the previous value from ever being read (lost computation). 1568 Preventing or detecting barging is an involved challenge with low-level locks, which is made easier through higher-level constructs. 1569 This challenge is often split into two different approaches: barging avoidance and prevention. 1570 Algorithms that unconditionally releasing a lock for competing threads to acquire use barging avoidance during synchronization to force a barging thread to wait; 1571 algorithms that conditionally hold locks during synchronization, \eg baton-passing~\cite{Andrews89}, prevent barging completely. 1572 1573 1574 \section{Monitor} 1575 \label{s:Monitor} 1576 1577 A \textbf{monitor} is a set of functions that ensure mutual exclusion when accessing shared state. 1578 More precisely, a monitor is a programming technique that implicitly binds mutual exclusion to static function scope, as opposed to locks, where mutual-exclusion is defined by acquire/release calls, independent of lexical context (analogous to block and heap storage allocation). 1579 Restricting acquire/release points eases programming, comprehension, and maintenance, at a slight cost in flexibility and efficiency. 1580 \CFA uses a custom @monitor@ type and leverages declaration semantics (deallocation) to protect active or waiting threads in a monitor. 1581 1582 The following is a \CFA monitor implementation of an atomic counter. 1583 \begin{cfa}[morekeywords=nomutex] 1584 `monitor` Aint { int cnt; }; $\C[4.25in]{// atomic integer counter}$ 1585 int ++?( Aint & `mutex`$\(_{opt}\)$ this ) with( this ) { return ++cnt; } $\C{// increment}$ 1586 int ?=?( Aint & `mutex`$\(_{opt}\)$ lhs, int rhs ) with( lhs ) { cnt = rhs; } $\C{// conversions with int}\CRT$ 1587 int ?=?( int & lhs, Aint & `mutex`$\(_{opt}\)$ rhs ) with( rhs ) { lhs = cnt; } 1588 \end{cfa} 1589 % The @Aint@ constructor, @?{}@, uses the \lstinline[morekeywords=nomutex]@nomutex@ qualifier indicating mutual exclusion is unnecessary during construction because an object is inaccessible (private) until after it is initialized. 1590 % (While a constructor may publish its address into a global variable, doing so generates a race-condition.) 1591 The prefix increment operation, @++?@, is normally @mutex@, indicating mutual exclusion is necessary during function execution, to protect the incrementing from race conditions, unless there is an atomic increment instruction for the implementation type. 1592 The assignment operators provide bidirectional conversion between an atomic and normal integer without accessing field @cnt@; 1593 these operations only need @mutex@, if reading/writing the implementation type is not atomic. 1594 The atomic counter is used without any explicit mutual-exclusion and provides thread-safe semantics, which is similar to the \CC template @std::atomic@. 1595 \begin{cfa} 1596 int i = 0, j = 0, k = 5; 1597 Aint x = { 0 }, y = { 0 }, z = { 5 }; $\C{// no mutex required}$ 1598 ++x; ++y; ++z; $\C{// safe increment by multiple threads}$ 1599 x = 2; y = i; z = k; $\C{// conversions}$ 1600 i = x; j = y; k = z; 1601 \end{cfa} 1602 1603 \CFA monitors have \newterm{multi-acquire} semantics so the thread in the monitor may acquire it multiple times without deadlock, allowing recursion and calling other interface functions. 1604 \begin{cfa} 1605 monitor M { ... } m; 1606 void foo( M & mutex m ) { ... } $\C{// acquire mutual exclusion}$ 1607 void bar( M & mutex m ) { $\C{// acquire mutual exclusion}$ 1608 ... `bar( m );` ... `foo( m );` ... $\C{// reacquire mutual exclusion}$ 1609 } 1610 \end{cfa} 1611 \CFA monitors also ensure the monitor lock is released regardless of how an acquiring function ends (normal or exceptional), and returning a shared variable is safe via copying before the lock is released. 1612 Similar safety is offered by \emph{explicit} mechanisms like \CC RAII; 1613 monitor \emph{implicit} safety ensures no programmer usage errors. 1614 Furthermore, RAII mechanisms cannot handle complex synchronization within a monitor, where the monitor lock may not be released on function exit because it is passed to an unblocking thread; 1615 RAII is purely a mutual-exclusion mechanism (see Section~\ref{s:Scheduling}). 1616 1617 1618 \subsection{Monitor Implementation} 1619 1620 For the same design reasons, \CFA provides a custom @monitor@ type and a @trait@ to enforce and restrict the monitor-interface functions. 1621 \begin{cquote} 1622 \begin{tabular}{@{}c@{\hspace{3\parindentlnth}}c@{}} 1623 \begin{cfa} 1624 monitor M { 1625 ... // shared data 1626 }; 1627 1628 \end{cfa} 1629 & 1260 Preventing or detecting barging is an involved challenge with low-level locks, which can be made much easier by higher-level constructs. 1261 This challenge is often split into two different approaches: barging avoidance and barging prevention. 1262 Algorithms that allow a barger, but divert it until later using current synchronization state (flags), are avoiding the barger; 1263 algorithms that preclude a barger from entering during synchronization in the critical section prevent barging completely. 1264 Techniques like baton-pass locks~\cite{Andrews89} between threads instead of unconditionally releasing locks is an example of barging prevention. 1265 1266 1267 \section{Monitors} 1268 \label{s:Monitors} 1269 1270 A \textbf{monitor} is a set of routines that ensure mutual exclusion when accessing shared state. 1271 More precisely, a monitor is a programming technique that binds mutual exclusion to routine scope, as opposed to locks, where mutual-exclusion is defined by acquire/release calls, independent of lexical context (analogous to block and heap storage allocation). 1272 The strong association with the call/return paradigm eases programmability, readability and maintainability, at a slight cost in flexibility and efficiency. 1273 1274 Note, like coroutines/threads, both locks and monitors require an abstract handle to reference them, because at their core, both mechanisms are manipulating non-copyable shared state. 1275 Copying a lock is insecure because it is possible to copy an open lock and then use the open copy when the original lock is closed to simultaneously access the shared data. 1276 Copying a monitor is secure because both the lock and shared data are copies, but copying the shared data is meaningless because it no longer represents a unique entity. 1277 As for coroutines/tasks, a non-copyable (@dtype@) trait is used to capture this requirement, so all locks/monitors must be passed by reference (pointer). 1630 1278 \begin{cfa} 1631 1279 trait is_monitor( `dtype` T ) { … … 1634 1282 }; 1635 1283 \end{cfa} 1636 \end{tabular}1637 \end{cquote}1638 The @dtype@ property prevents \emph{implicit} copy operations and the @is_monitor@ trait provides no \emph{explicit} copy operations, so monitors must be passed by reference (pointer).1639 % Copying a lock is insecure because it is possible to copy an open lock and then use the open copy when the original lock is closed to simultaneously access the shared data.1640 % Copying a monitor is secure because both the lock and shared data are copies, but copying the shared data is meaningless because it no longer represents a unique entity.1641 Similarly, the function definitions ensures there is a mechanism to get (read) the monitor descriptor from its handle, and a special destructor to prevent deallocation if a thread using the shared data.1642 The custom monitor type also inserts any locks needed to implement the mutual exclusion semantics.1643 1284 1644 1285 … … 1646 1287 \label{s:MutexAcquisition} 1647 1288 1648 While the monitor lock provides mutual exclusion for shared data, there are implementation options forwhen and where the locking/unlocking occurs.1289 While correctness implicitly implies a monitor's mutual exclusion is acquired and released, there are implementation options about when and where the locking/unlocking occurs. 1649 1290 (Much of this discussion also applies to basic locks.) 1650 For example, a monitor may be passed through multiple helper functions before it is necessary to acquire the monitor's mutual exclusion. 1651 1652 The benefit of mandatory monitor qualifiers is self-documentation, but requiring both @mutex@ and \lstinline[morekeywords=nomutex]@nomutex@ for all monitor parameters is redundant. 1653 Instead, the semantics has one qualifier as the default and the other required. 1654 For example, make the safe @mutex@ qualifier the default because assuming \lstinline[morekeywords=nomutex]@nomutex@ may cause subtle errors. 1655 Alternatively, make the unsafe \lstinline[morekeywords=nomutex]@nomutex@ qualifier the default because it is the \emph{normal} parameter semantics while @mutex@ parameters are rare. 1291 For example, a monitor may need to be passed through multiple helper routines before it becomes necessary to acquire the monitor mutual-exclusion. 1292 \begin{cfa}[morekeywords=nomutex] 1293 monitor Aint { int cnt; }; $\C{// atomic integer counter}$ 1294 void ?{}( Aint & `nomutex` this ) with( this ) { cnt = 0; } $\C{// constructor}$ 1295 int ?=?( Aint & `mutex`$\(_{opt}\)$ lhs, int rhs ) with( lhs ) { cnt = rhs; } $\C{// conversions}$ 1296 void ?{}( int & this, Aint & `mutex`$\(_{opt}\)$ v ) { this = v.cnt; } 1297 int ?=?( int & lhs, Aint & `mutex`$\(_{opt}\)$ rhs ) with( rhs ) { lhs = cnt; } 1298 int ++?( Aint & `mutex`$\(_{opt}\)$ this ) with( this ) { return ++cnt; } $\C{// increment}$ 1299 \end{cfa} 1300 The @Aint@ constructor, @?{}@, uses the \lstinline[morekeywords=nomutex]@nomutex@ qualifier indicating mutual exclusion is unnecessary during construction because an object is inaccessible (private) until after it is initialized. 1301 (While a constructor may publish its address into a global variable, doing so generates a race-condition.) 1302 The conversion operators for initializing and assigning with a normal integer only need @mutex@, if reading/writing the implementation type is not atomic. 1303 Finally, the prefix increment operato, @++?@, is normally @mutex@ to protect the incrementing from race conditions, unless there is an atomic increment instruction for the implementation type. 1304 1305 The atomic counter is used without any explicit mutual-exclusion and provides thread-safe semantics, which is similar to the \CC template @std::atomic@. 1306 \begin{cfa} 1307 Aint x, y, z; 1308 ++x; ++y; ++z; $\C{// safe increment by multiple threads}$ 1309 x = 2; y = 2; z = 2; $\C{// conversions}$ 1310 int i = x, j = y, k = z; 1311 i = x; j = y; k = z; 1312 \end{cfa} 1313 1314 For maximum usability, monitors have \newterm{multi-acquire} semantics allowing a thread to acquire it multiple times without deadlock. 1315 For example, atomically printing the contents of a binary tree: 1316 \begin{cfa} 1317 monitor Tree { 1318 Tree * left, right; 1319 // value 1320 }; 1321 void print( Tree & mutex tree ) { $\C{// prefix traversal}$ 1322 // write value 1323 print( tree->left ); $\C{// multiply acquire monitor lock on each recursion}$ 1324 print( tree->right ); 1325 } 1326 \end{cfa} 1327 1328 Mandatory monitor qualifiers have the benefit of being self-documented, but requiring both @mutex@ and \lstinline[morekeywords=nomutex]@nomutex@ for all monitor parameter is redundant. 1329 Instead, one of qualifier semantics can be the default, and the other required. 1330 For example, assume the safe @mutex@ option for a monitor parameter because assuming \lstinline[morekeywords=nomutex]@nomutex@ may cause subtle errors. 1331 On the other hand, assuming \lstinline[morekeywords=nomutex]@nomutex@ is the \emph{normal} parameter behaviour, stating explicitly ``this parameter is not special''. 1656 1332 Providing a default qualifier implies knowing whether a parameter is a monitor. 1657 Since \CFA relies heavily on traits as an abstraction mechanism, t ypes can coincidentally match the monitor trait but not be a monitor, similar to inheritance where a shape and playing card can both be drawable.1333 Since \CFA relies heavily on traits as an abstraction mechanism, the distinction between a type that is a monitor and a type that looks like a monitor can become blurred. 1658 1334 For this reason, \CFA requires programmers to identify the kind of parameter with the @mutex@ keyword and uses no keyword to mean \lstinline[morekeywords=nomutex]@nomutex@. 1659 1335 1660 1336 The next semantic decision is establishing which parameter \emph{types} may be qualified with @mutex@. 1661 The following has monitor parameter types that are composed of multiple objects. 1337 Given: 1662 1338 \begin{cfa} 1663 1339 monitor M { ... } 1664 int f1( M & mutex m ); $\C{// single parameter object}$ 1665 int f2( M * mutex m ); $\C{// single or multiple parameter object}$ 1666 int f3( M * mutex m[$\,$] ); $\C{// multiple parameter object}$ 1667 int f4( stack( M * ) & mutex m ); $\C{// multiple parameters object}$ 1668 \end{cfa} 1669 Function @f1@ has a single parameter object, while @f2@'s indirection could be a single or multi-element array, where static array size is often unknown in C. 1670 Function @f3@ has a multiple object matrix, and @f4@ a multiple object data structure. 1671 While shown shortly, multiple object acquisition is possible, but the number of objects must be statically known. 1672 Therefore, \CFA only acquires one monitor per parameter with at most one level of indirection, excluding pointers as it is impossible to statically determine the size. 1673 1674 For object-oriented monitors, \eg Java, calling a mutex member \emph{implicitly} acquires mutual exclusion of the receiver object, @`rec`.foo(...)@. 1675 \CFA has no receiver, and hence, the explicit @mutex@ qualifier is used to specify which objects acquire mutual exclusion. 1676 A positive consequence of this design decision is the ability to support multi-monitor functions,\footnote{ 1677 While object-oriented monitors can be extended with a mutex qualifier for multiple-monitor members, no prior example of this feature could be found.} 1678 called \newterm{bulk acquire}. 1679 \CFA guarantees acquisition order is consistent across calls to @mutex@ functions using the same monitors as arguments, so acquiring multiple monitors is safe from deadlock. 1680 Figure~\ref{f:BankTransfer} shows a trivial solution to the bank transfer problem~\cite{BankTransfer}, where two resources must be locked simultaneously, using \CFA monitors with implicit locking and \CC with explicit locking. 1681 A \CFA programmer only has to manage when to acquire mutual exclusion; 1682 a \CC programmer must select the correct lock and acquisition mechanism from a panoply of locking options. 1683 Making good choices for common cases in \CFA simplifies the programming experience and enhances safety. 1684 1685 \begin{figure} 1686 \centering 1687 \begin{lrbox}{\myboxA} 1688 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1689 monitor BankAccount { 1690 1691 int balance; 1692 } b1 = { 0 }, b2 = { 0 }; 1693 void deposit( BankAccount & `mutex` b, 1694 int deposit ) with(b) { 1695 balance += deposit; 1696 } 1697 void transfer( BankAccount & `mutex` my, 1698 BankAccount & `mutex` your, int me2you ) { 1699 1700 deposit( my, -me2you ); // debit 1701 deposit( your, me2you ); // credit 1702 } 1703 `thread` Person { BankAccount & b1, & b2; }; 1704 void main( Person & person ) with(person) { 1705 for ( 10_000_000 ) { 1706 if ( random() % 3 ) deposit( b1, 3 ); 1707 if ( random() % 3 ) transfer( b1, b2, 7 ); 1708 } 1709 } 1710 int main() { 1711 `Person p1 = { b1, b2 }, p2 = { b2, b1 };` 1712 1713 } // wait for threads to complete 1714 \end{cfa} 1715 \end{lrbox} 1716 1717 \begin{lrbox}{\myboxB} 1718 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1719 struct BankAccount { 1720 `recursive_mutex m;` 1721 int balance = 0; 1722 } b1, b2; 1723 void deposit( BankAccount & b, int deposit ) { 1724 `scoped_lock lock( b.m );` 1725 b.balance += deposit; 1726 } 1727 void transfer( BankAccount & my, 1728 BankAccount & your, int me2you ) { 1729 `scoped_lock lock( my.m, your.m );` 1730 deposit( my, -me2you ); // debit 1731 deposit( your, me2you ); // credit 1732 } 1733 1734 void person( BankAccount & b1, BankAccount & b2 ) { 1735 for ( int i = 0; i < 10$'$000$'$000; i += 1 ) { 1736 if ( random() % 3 ) deposit( b1, 3 ); 1737 if ( random() % 3 ) transfer( b1, b2, 7 ); 1738 } 1739 } 1740 int main() { 1741 `thread p1(person, ref(b1), ref(b2)), p2(person, ref(b2), ref(b1));` 1742 `p1.join(); p2.join();` 1743 } 1744 \end{cfa} 1745 \end{lrbox} 1746 1747 \subfloat[\CFA]{\label{f:CFABank}\usebox\myboxA} 1748 \hspace{3pt} 1749 \vrule 1750 \hspace{3pt} 1751 \subfloat[\CC]{\label{f:C++Bank}\usebox\myboxB} 1752 \hspace{3pt} 1753 \caption{Bank transfer problem} 1754 \label{f:BankTransfer} 1755 \end{figure} 1756 1757 Users can still force the acquiring order by using @mutex@/\lstinline[morekeywords=nomutex]@nomutex@. 1758 \begin{cfa} 1759 void foo( M & mutex m1, M & mutex m2 ); $\C{// acquire m1 and m2}$ 1340 int f1( M & mutex m ); 1341 int f2( M * mutex m ); 1342 int f3( M * mutex m[] ); 1343 int f4( stack( M * ) & mutex m ); 1344 \end{cfa} 1345 the issue is that some of these parameter types are composed of multiple objects. 1346 For @f1@, there is only a single parameter object. 1347 Adding indirection in @f2@ still identifies a single object. 1348 However, the matrix in @f3@ introduces multiple objects. 1349 While shown shortly, multiple acquisition is possible; 1350 however array lengths are often unknown in C. 1351 This issue is exacerbated in @f4@, where the data structure must be safely traversed to acquire all of its elements. 1352 1353 To make the issue tractable, \CFA only acquires one monitor per parameter with at most one level of indirection. 1354 However, the C type-system has an ambiguity with respects to arrays. 1355 Is the argument for @f2@ a single object or an array of objects? 1356 If it is an array, only the first element of the array is acquired, which seems unsafe; 1357 hence, @mutex@ is disallowed for array parameters. 1358 \begin{cfa} 1359 int f1( M & mutex m ); $\C{// allowed: recommended case}$ 1360 int f2( M * mutex m ); $\C{// disallowed: could be an array}$ 1361 int f3( M mutex m[$\,$] ); $\C{// disallowed: array length unknown}$ 1362 int f4( M ** mutex m ); $\C{// disallowed: could be an array}$ 1363 int f5( M * mutex m[$\,$] ); $\C{// disallowed: array length unknown}$ 1364 \end{cfa} 1365 % Note, not all array routines have distinct types: @f2@ and @f3@ have the same type, as do @f4@ and @f5@. 1366 % However, even if the code generation could tell the difference, the extra information is still not sufficient to extend meaningfully the monitor call semantic. 1367 1368 For object-oriented monitors, calling a mutex member \emph{implicitly} acquires mutual exclusion of the receiver object, @`rec`.foo(...)@. 1369 \CFA has no receiver, and hence, must use an explicit mechanism to specify which object has mutual exclusion acquired. 1370 A positive consequence of this design decision is the ability to support multi-monitor routines. 1371 \begin{cfa} 1372 int f( M & mutex x, M & mutex y ); $\C{// multiple monitor parameter of any type}$ 1373 M m1, m2; 1374 f( m1, m2 ); 1375 \end{cfa} 1376 (While object-oriented monitors can be extended with a mutex qualifier for multiple-monitor members, no prior example of this feature could be found.) 1377 In practice, writing multi-locking routines that do not deadlocks is tricky. 1378 Having language support for such a feature is therefore a significant asset for \CFA. 1379 1380 The capability to acquire multiple locks before entering a critical section is called \newterm{bulk acquire}. 1381 In previous example, \CFA guarantees the order of acquisition is consistent across calls to different routines using the same monitors as arguments. 1382 This consistent ordering means acquiring multiple monitors is safe from deadlock. 1383 However, users can force the acquiring order. 1384 For example, notice the use of @mutex@/\lstinline[morekeywords=nomutex]@nomutex@ and how this affects the acquiring order: 1385 \begin{cfa} 1386 void foo( M & mutex m1, M & mutex m2 ); $\C{// acquire m1 and m2}$ 1760 1387 void bar( M & mutex m1, M & /* nomutex */ m2 ) { $\C{// acquire m1}$ 1761 ... foo( m1, m2 ); ... $\C{// acquire m2}$1388 ... foo( m1, m2 ); ... $\C{// acquire m2}$ 1762 1389 } 1763 1390 void baz( M & /* nomutex */ m1, M & mutex m2 ) { $\C{// acquire m2}$ 1764 ... foo( m1, m2 ); ... $\C{// acquire m1}$ 1765 } 1766 \end{cfa} 1767 The bulk-acquire semantics allow @bar@ or @baz@ to acquire a monitor lock and reacquire it in @foo@. 1768 The calls to @bar@ and @baz@ acquired the monitors in opposite order, possibly resulting in deadlock. 1769 However, this case is the simplest instance of the \emph{nested-monitor problem}~\cite{Lister77}, where monitors are acquired in sequence versus bulk. 1770 Detecting the nested-monitor problem requires dynamic tracking of monitor calls, and dealing with it requires rollback semantics~\cite{Dice10}. 1771 \CFA does not deal with this fundamental problem. 1772 1773 Finally, like Java, \CFA offers an alternative @mutex@ statement to reduce refactoring and naming. 1391 ... foo( m1, m2 ); ... $\C{// acquire m1}$ 1392 } 1393 \end{cfa} 1394 The multi-acquire semantics allows @bar@ or @baz@ to acquire a monitor lock and reacquire it in @foo@. 1395 In the calls to @bar@ and @baz@, the monitors are acquired in opposite order. 1396 1397 However, such use leads to lock acquiring order problems resulting in deadlock~\cite{Lister77}, where detecting it requires dynamically tracking of monitor calls, and dealing with it requires implement rollback semantics~\cite{Dice10}. 1398 In \CFA, safety is guaranteed by using bulk acquire of all monitors to shared objects, whereas other monitor systems provide no aid. 1399 While \CFA provides only a partial solution, the \CFA partial solution handles many useful cases. 1400 \begin{cfa} 1401 monitor Bank { ... }; 1402 void deposit( Bank & `mutex` b, int deposit ); 1403 void transfer( Bank & `mutex` mybank, Bank & `mutex` yourbank, int me2you) { 1404 deposit( mybank, `-`me2you ); $\C{// debit}$ 1405 deposit( yourbank, me2you ); $\C{// credit}$ 1406 } 1407 \end{cfa} 1408 This example shows a trivial solution to the bank-account transfer problem~\cite{BankTransfer}. 1409 Without multi- and bulk acquire, the solution to this problem requires careful engineering. 1410 1411 1412 \subsection{\protect\lstinline|mutex| statement} \label{mutex-stmt} 1413 1414 The monitor call-semantics associate all locking semantics to routines. 1415 Like Java, \CFA offers an alternative @mutex@ statement to reduce refactoring and naming. 1774 1416 \begin{cquote} 1775 \renewcommand{\arraystretch}{0.0} 1776 \begin{tabular}{@{}l@{\hspace{3\parindentlnth}}l@{}} 1777 \multicolumn{1}{c}{\textbf{\lstinline@mutex@ call}} & \multicolumn{1}{c}{\lstinline@mutex@ \textbf{statement}} \\ 1778 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1779 monitor M { ... }; 1417 \begin{tabular}{@{}c|@{\hspace{\parindentlnth}}c@{}} 1418 routine call & @mutex@ statement \\ 1419 \begin{cfa} 1420 monitor M {}; 1780 1421 void foo( M & mutex m1, M & mutex m2 ) { 1781 1422 // critical section … … 1786 1427 \end{cfa} 1787 1428 & 1788 \begin{cfa} [aboveskip=0pt,belowskip=0pt]1429 \begin{cfa} 1789 1430 1790 1431 void bar( M & m1, M & m2 ) { … … 1799 1440 1800 1441 1801 \subsection{Scheduling} 1802 \label{s:Scheduling} 1803 1804 % There are many aspects of scheduling in a concurrency system, all related to resource utilization by waiting threads, \ie which thread gets the resource next. 1805 % Different forms of scheduling include access to processors by threads (see Section~\ref{s:RuntimeStructureCluster}), another is access to a shared resource by a lock or monitor. 1806 This section discusses monitor scheduling for waiting threads eligible for entry, \ie which thread gets the shared resource next. (See Section~\ref{s:RuntimeStructureCluster} for scheduling threads on virtual processors.) 1807 While monitor mutual-exclusion provides safe access to shared data, the monitor data may indicate that a thread accessing it cannot proceed, \eg a bounded buffer may be full/empty so produce/consumer threads must block. 1442 \section{Internal Scheduling} 1443 \label{s:InternalScheduling} 1444 1445 While monitor mutual-exclusion provides safe access to shared data, the monitor data may indicate that a thread accessing it cannot proceed, \eg a bounded buffer, Figure~\ref{f:GenericBoundedBuffer}, may be full/empty so produce/consumer threads must block. 1808 1446 Leaving the monitor and trying again (busy waiting) is impractical for high-level programming. 1809 Monitors eliminate busy waiting by providing synchronization to schedule threads needing access to the shared data, where threads blockversus spinning.1810 Synchronization is generally achieved with internal~\cite{Hoare74} or external~\cite[\S~2.9.2]{uC++} scheduling.1447 Monitors eliminate busy waiting by providing internal synchronization to schedule threads needing access to the shared data, where the synchronization is blocking (threads are parked) versus spinning. 1448 The synchronization is generally achieved with internal~\cite{Hoare74} or external~\cite[\S~2.9.2]{uC++} scheduling, where \newterm{scheduling} is defined as indicating which thread acquires the critical section next. 1811 1449 \newterm{Internal scheduling} is characterized by each thread entering the monitor and making an individual decision about proceeding or blocking, while \newterm{external scheduling} is characterized by an entering thread making a decision about proceeding for itself and on behalf of other threads attempting entry. 1812 Finally, \CFA monitors do not allow calling threads to barge ahead of signalled threads, which simplifies synchronization among threads in the monitor and increases correctness. 1813 If barging is allowed, synchronization between a signaller and signallee is difficult, often requiring additional flags and multiple unblock/block cycles. 1814 In fact, signals-as-hints is completely opposite from that proposed by Hoare in the seminal paper on monitors~\cite[p.~550]{Hoare74}. 1815 % \begin{cquote} 1816 % However, we decree that a signal operation be followed immediately by resumption of a waiting program, without possibility of an intervening procedure call from yet a third program. 1817 % It is only in this way that a waiting program has an absolute guarantee that it can acquire the resource just released by the signalling program without any danger that a third program will interpose a monitor entry and seize the resource instead.~\cite[p.~550]{Hoare74} 1818 % \end{cquote} 1819 Furthermore, \CFA concurrency has no spurious wakeup~\cite[\S~9]{Buhr05a}, which eliminates an implicit form of self barging. 1820 Hence, a \CFA @wait@ statement is not enclosed in a @while@ loop retesting a blocking predicate, which can cause thread starvation due to barging. 1821 1822 Figure~\ref{f:MonitorScheduling} shows general internal/external scheduling (for the bounded-buffer example in Figure~\ref{f:InternalExternalScheduling}). 1823 External calling threads block on the calling queue, if the monitor is occupied, otherwise they enter in FIFO order. 1824 Internal threads block on condition queues via @wait@ and reenter from the condition in FIFO order. 1825 Alternatively, internal threads block on urgent from the @signal_block@ or @waitfor@, and reenter implicitly when the monitor becomes empty, \ie, the thread in the monitor exits or waits. 1826 1827 There are three signalling mechanisms to unblock waiting threads to enter the monitor. 1828 Note, signalling cannot have the signaller and signalled thread in the monitor simultaneously because of the mutual exclusion, so either the signaller or signallee can proceed. 1829 For internal scheduling, threads are unblocked from condition queues using @signal@, where the signallee is moved to urgent and the signaller continues (solid line). 1830 Multiple signals move multiple signallees to urgent until the condition is empty. 1831 When the signaller exits or waits, a thread blocked on urgent is processed before calling threads to prevent barging. 1832 (Java conceptually moves the signalled thread to the calling queue, and hence, allows barging.) 1833 The alternative unblock is in the opposite order using @signal_block@, where the signaller is moved to urgent and the signallee continues (dashed line), and is implicitly unblocked from urgent when the signallee exits or waits. 1834 1835 For external scheduling, the condition queues are not used; 1836 instead threads are unblocked directly from the calling queue using @waitfor@ based on function names requesting mutual exclusion. 1837 (The linear search through the calling queue to locate a particular call can be reduced to $O(1)$.) 1838 The @waitfor@ has the same semantics as @signal_block@, where the signalled thread executes before the signallee, which waits on urgent. 1839 Executing multiple @waitfor@s from different signalled functions causes the calling threads to move to urgent. 1840 External scheduling requires urgent to be a stack, because the signaller expects to execute immediately after the specified monitor call has exited or waited. 1841 Internal scheduling behaves the same for an urgent stack or queue, except for multiple signalling, where the threads unblock from urgent in reverse order from signalling. 1842 If the restart order is important, multiple signalling by a signal thread can be transformed into daisy-chain signalling among threads, where each thread signals the next thread. 1843 We tried both a stack for @waitfor@ and queue for signalling, but that resulted in complex semantics about which thread enters next. 1844 Hence, \CFA uses a single urgent stack to correctly handle @waitfor@ and adequately support both forms of signalling. 1845 1846 \begin{figure} 1847 \centering 1848 % \subfloat[Scheduling Statements] { 1849 % \label{fig:SchedulingStatements} 1850 % {\resizebox{0.45\textwidth}{!}{\input{CondSigWait.pstex_t}}} 1851 \input{CondSigWait.pstex_t} 1852 % }% subfloat 1853 % \quad 1854 % \subfloat[Bulk acquire monitor] { 1855 % \label{fig:BulkMonitor} 1856 % {\resizebox{0.45\textwidth}{!}{\input{ext_monitor.pstex_t}}} 1857 % }% subfloat 1858 \caption{Monitor Scheduling} 1859 \label{f:MonitorScheduling} 1860 \end{figure} 1861 1862 Figure~\ref{f:BBInt} shows a \CFA generic bounded-buffer with internal scheduling, where producers/consumers enter the monitor, detect the buffer is full/empty, and block on an appropriate condition variable, @full@/@empty@. 1863 The @wait@ function atomically blocks the calling thread and implicitly releases the monitor lock(s) for all monitors in the function's parameter list. 1864 The appropriate condition variable is signalled to unblock an opposite kind of thread after an element is inserted/removed from the buffer. 1865 Signalling is unconditional, because signalling an empty condition variable does nothing. 1866 It is common to declare condition variables as monitor fields to prevent shared access, hence no locking is required for access as the conditions are protected by the monitor lock. 1867 In \CFA, a condition variable can be created/stored independently. 1868 % To still prevent expensive locking on access, a condition variable is tied to a \emph{group} of monitors on first use, called \newterm{branding}, resulting in a low-cost boolean test to detect sharing from other monitors. 1869 1870 % Signalling semantics cannot have the signaller and signalled thread in the monitor simultaneously, which means: 1871 % \begin{enumerate} 1872 % \item 1873 % The signalling thread returns immediately and the signalled thread continues. 1874 % \item 1875 % The signalling thread continues and the signalled thread is marked for urgent unblocking at the next scheduling point (exit/wait). 1876 % \item 1877 % The signalling thread blocks but is marked for urgent unblocking at the next scheduling point and the signalled thread continues. 1878 % \end{enumerate} 1879 % The first approach is too restrictive, as it precludes solving a reasonable class of problems, \eg dating service (see Figure~\ref{f:DatingService}). 1880 % \CFA supports the next two semantics as both are useful. 1450 1451 Figure~\ref{f:BBInt} shows a \CFA bounded-buffer with internal scheduling, where producers/consumers enter the monitor, see the buffer is full/empty, and block on an appropriate condition lock, @full@/@empty@. 1452 The @wait@ routine atomically blocks the calling thread and implicitly releases the monitor lock(s) for all monitors in the routine's parameter list. 1453 The appropriate condition lock is signalled to unblock an opposite kind of thread after an element is inserted/removed from the buffer. 1454 Signalling is unconditional, because signalling an empty condition lock does nothing. 1455 Signalling semantics cannot have the signaller and signalled thread in the monitor simultaneously, which means: 1456 \begin{enumerate} 1457 \item 1458 The signalling thread returns immediately, and the signalled thread continues. 1459 \item 1460 The signalling thread continues and the signalled thread is marked for urgent unblocking at the next scheduling point (exit/wait). 1461 \item 1462 The signalling thread blocks but is marked for urgrent unblocking at the next scheduling point and the signalled thread continues. 1463 \end{enumerate} 1464 The first approach is too restrictive, as it precludes solving a reasonable class of problems (\eg dating service). 1465 \CFA supports the next two semantics as both are useful. 1466 Finally, while it is common to store a @condition@ as a field of the monitor, in \CFA, a @condition@ variable can be created/stored independently. 1467 Furthermore, a condition variable is tied to a \emph{group} of monitors on first use (called \newterm{branding}), which means that using internal scheduling with distinct sets of monitors requires one condition variable per set of monitors. 1881 1468 1882 1469 \begin{figure} … … 1892 1479 }; 1893 1480 void ?{}( Buffer(T) & buffer ) with(buffer) { 1894 front = back = count= 0;1481 [front, back, count] = 0; 1895 1482 } 1483 1896 1484 void insert( Buffer(T) & mutex buffer, T elem ) 1897 1485 with(buffer) { … … 1910 1498 \end{lrbox} 1911 1499 1912 % \newbox\myboxB1913 % \begin{lrbox}{\myboxB}1914 % \begin{cfa}[aboveskip=0pt,belowskip=0pt]1915 % forall( otype T ) { // distribute forall1916 % monitor Buffer {1917 %1918 % int front, back, count;1919 % T elements[10];1920 % };1921 % void ?{}( Buffer(T) & buffer ) with(buffer) {1922 % [front, back, count] = 0;1923 % }1924 % T remove( Buffer(T) & mutex buffer ); // forward1925 % void insert( Buffer(T) & mutex buffer, T elem )1926 % with(buffer) {1927 % if ( count == 10 ) `waitfor( remove, buffer )`;1928 % // insert elem into buffer1929 %1930 % }1931 % T remove( Buffer(T) & mutex buffer ) with(buffer) {1932 % if ( count == 0 ) `waitfor( insert, buffer )`;1933 % // remove elem from buffer1934 %1935 % return elem;1936 % }1937 % }1938 % \end{cfa}1939 % \end{lrbox}1940 1941 1500 \newbox\myboxB 1942 1501 \begin{lrbox}{\myboxB} 1943 1502 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1944 monitor ReadersWriter { 1945 int rcnt, wcnt; // readers/writer using resource 1946 }; 1947 void ?{}( ReadersWriter & rw ) with(rw) { 1948 rcnt = wcnt = 0; 1949 } 1950 void EndRead( ReadersWriter & mutex rw ) with(rw) { 1951 rcnt -= 1; 1952 } 1953 void EndWrite( ReadersWriter & mutex rw ) with(rw) { 1954 wcnt = 0; 1955 } 1956 void StartRead( ReadersWriter & mutex rw ) with(rw) { 1957 if ( wcnt > 0 ) `waitfor( EndWrite, rw );` 1958 rcnt += 1; 1959 } 1960 void StartWrite( ReadersWriter & mutex rw ) with(rw) { 1961 if ( wcnt > 0 ) `waitfor( EndWrite, rw );` 1962 else while ( rcnt > 0 ) `waitfor( EndRead, rw );` 1963 wcnt = 1; 1964 } 1965 1503 forall( otype T ) { // distribute forall 1504 monitor Buffer { 1505 1506 int front, back, count; 1507 T elements[10]; 1508 }; 1509 void ?{}( Buffer(T) & buffer ) with(buffer) { 1510 [front, back, count] = 0; 1511 } 1512 T remove( Buffer(T) & mutex buffer ); // forward 1513 void insert( Buffer(T) & mutex buffer, T elem ) 1514 with(buffer) { 1515 if ( count == 10 ) `waitfor( remove, buffer )`; 1516 // insert elem into buffer 1517 1518 } 1519 T remove( Buffer(T) & mutex buffer ) with(buffer) { 1520 if ( count == 0 ) `waitfor( insert, buffer )`; 1521 // remove elem from buffer 1522 1523 return elem; 1524 } 1525 } 1966 1526 \end{cfa} 1967 1527 \end{lrbox} 1968 1528 1969 \subfloat[Generic bounded buffer, internal scheduling]{\label{f:BBInt}\usebox\myboxA} 1970 \hspace{3pt} 1971 \vrule 1972 \hspace{3pt} 1973 \subfloat[Readers / writer lock, external scheduling]{\label{f:RWExt}\usebox\myboxB} 1974 1975 \caption{Internal / external scheduling} 1976 \label{f:InternalExternalScheduling} 1529 \subfloat[Internal Scheduling]{\label{f:BBInt}\usebox\myboxA} 1530 %\qquad 1531 \subfloat[External Scheduling]{\label{f:BBExt}\usebox\myboxB} 1532 \caption{Generic Bounded-Buffer} 1533 \label{f:GenericBoundedBuffer} 1977 1534 \end{figure} 1978 1535 1979 Figure~\ref{f:BBInt} can be transformed into external scheduling by removing the condition variables and signals/waits, and adding the following lines at the locations of the current @wait@s in @insert@/@remove@, respectively. 1980 \begin{cfa}[aboveskip=2pt,belowskip=1pt] 1981 if ( count == 10 ) `waitfor( remove, buffer )`; | if ( count == 0 ) `waitfor( insert, buffer )`; 1982 \end{cfa} 1983 Here, the producers/consumers detects a full/\-empty buffer and prevents more producers/consumers from entering the monitor until there is a free/empty slot in the buffer. 1984 External scheduling is controlled by the @waitfor@ statement, which atomically blocks the calling thread, releases the monitor lock, and restricts the function calls that can next acquire mutual exclusion. 1536 Figure~\ref{f:BBExt} shows a \CFA bounded-buffer with external scheduling, where producers/consumers detecting a full/empty buffer block and prevent more producers/consumers from entering the monitor until the buffer has a free/empty slot. 1537 External scheduling is controlled by the @waitfor@ statement, which atomically blocks the calling thread, releases the monitor lock, and restricts the routine calls that can next acquire mutual exclusion. 1985 1538 If the buffer is full, only calls to @remove@ can acquire the buffer, and if the buffer is empty, only calls to @insert@ can acquire the buffer. 1986 Threads calling excluded functions block outside of (external to) the monitor on the calling queue, versus blocking on condition queues inside of (internal to) the monitor. 1987 Figure~\ref{f:RWExt} shows a readers/writer lock written using external scheduling, where a waiting reader detects a writer using the resource and restricts further calls until the writer exits by calling @EndWrite@. 1988 The writer does a similar action for each reader or writer using the resource. 1989 Note, no new calls to @StarRead@/@StartWrite@ may occur when waiting for the call to @EndRead@/@EndWrite@. 1990 External scheduling allows waiting for events from other threads while restricting unrelated events, that would otherwise have to wait on conditions in the monitor. 1991 The mechnaism can be done in terms of control flow, \eg Ada @accept@ or \uC @_Accept@, or in terms of data, \eg Go @select@ on channels. 1992 While both mechanisms have strengths and weaknesses, this project uses the control-flow mechanism to be consistent with other language features. 1993 % Two challenges specific to \CFA for external scheduling are loose object-definitions (see Section~\ref{s:LooseObjectDefinitions}) and multiple-monitor functions (see Section~\ref{s:Multi-MonitorScheduling}). 1994 1995 Figure~\ref{f:DatingService} shows a dating service demonstrating non-blocking and blocking signalling. 1996 The dating service matches girl and boy threads with matching compatibility codes so they can exchange phone numbers. 1997 A thread blocks until an appropriate partner arrives. 1998 The complexity is exchanging phone numbers in the monitor because of the mutual-exclusion property. 1999 For signal scheduling, the @exchange@ condition is necessary to block the thread finding the match, while the matcher unblocks to take the opposite number, post its phone number, and unblock the partner. 2000 For signal-block scheduling, the implicit urgent-queue replaces the explict @exchange@-condition and @signal_block@ puts the finding thread on the urgent condition and unblocks the matcher. 2001 The dating service is an example of a monitor that cannot be written using external scheduling because it requires knowledge of calling parameters to make scheduling decisions, and parameters of waiting threads are unavailable; 2002 as well, an arriving thread may not find a partner and must wait, which requires a condition variable, and condition variables imply internal scheduling. 2003 Furthermore, barging corrupts the dating service during an exchange because a barger may also match and change the phone numbers, invalidating the previous exchange phone number. 2004 Putting loops around the @wait@s does not correct the problem; 2005 the simple solution must be restructured to account for barging. 1539 Threads making calls to routines that are currently excluded block outside (externally) of the monitor on a calling queue, versus blocking on condition queues inside the monitor. 1540 1541 Both internal and external scheduling extend to multiple monitors in a natural way. 1542 \begin{cfa} 1543 monitor M { `condition e`; ... }; 1544 void foo( M & mutex m1, M & mutex m2 ) { 1545 ... wait( `e` ); ... $\C{// wait( e, m1, m2 )}$ 1546 ... wait( `e, m1` ); ... 1547 ... wait( `e, m2` ); ... 1548 } 1549 1550 void rtn$\(_1\)$( M & mutex m1, M & mutex m2 ); 1551 void rtn$\(_2\)$( M & mutex m1 ); 1552 void bar( M & mutex m1, M & mutex m2 ) { 1553 ... waitfor( `rtn` ); ... $\C{// waitfor( rtn\(_1\), m1, m2 )}$ 1554 ... waitfor( `rtn, m1` ); ... $\C{// waitfor( rtn\(_2\), m1 )}$ 1555 } 1556 \end{cfa} 1557 For @wait( e )@, the default semantics is to atomically block the signaller and release all acquired mutex types in the parameter list, \ie @wait( e, m1, m2 )@. 1558 To override the implicit multi-monitor wait, specific mutex parameter(s) can be specified, \eg @wait( e, m1 )@. 1559 Wait statically verifies the released monitors are the acquired mutex-parameters so unconditional release is safe. 1560 Similarly, for @waitfor( rtn, ... )@, the default semantics is to atomically block the acceptor and release all acquired mutex types in the parameter list, \ie @waitfor( rtn, m1, m2 )@. 1561 To override the implicit multi-monitor wait, specific mutex parameter(s) can be specified, \eg @waitfor( rtn, m1 )@. 1562 Waitfor statically verifies the released monitors are the same as the acquired mutex-parameters of the given routine or routine pointer. 1563 To statically verify the released monitors match with the accepted routine's mutex parameters, the routine (pointer) prototype must be accessible. 1564 1565 Given the ability to release a subset of acquired monitors can result in a \newterm{nested monitor}~\cite{Lister77} deadlock. 1566 \begin{cfa} 1567 void foo( M & mutex m1, M & mutex m2 ) { 1568 ... wait( `e, m1` ); ... $\C{// release m1, keeping m2 acquired )}$ 1569 void baz( M & mutex m1, M & mutex m2 ) { $\C{// must acquire m1 and m2 )}$ 1570 ... signal( `e` ); ... 1571 \end{cfa} 1572 The @wait@ only releases @m1@ so the signalling thread cannot acquire both @m1@ and @m2@ to enter @baz@ to get to the @signal@. 1573 While deadlock issues can occur with multiple/nesting acquisition, this issue results from the fact that locks, and by extension monitors, are not perfectly composable. 1574 1575 Finally, an important aspect of monitor implementation is barging, \ie can calling threads barge ahead of signalled threads? 1576 If barging is allowed, synchronization between a singller and signallee is difficult, often requiring multiple unblock/block cycles (looping around a wait rechecking if a condition is met). 1577 \begin{quote} 1578 However, we decree that a signal operation be followed immediately by resumption of a waiting program, without possibility of an intervening procedure call from yet a third program. 1579 It is only in this way that a waiting program has an absolute guarantee that it can acquire the resource just released by the signalling program without any danger that a third program will interpose a monitor entry and seize the resource instead.~\cite[p.~550]{Hoare74} 1580 \end{quote} 1581 \CFA scheduling \emph{precludes} barging, which simplifies synchronization among threads in the monitor and increases correctness. 1582 For example, there are no loops in either bounded buffer solution in Figure~\ref{f:GenericBoundedBuffer}. 1583 Supporting barging prevention as well as extending internal scheduling to multiple monitors is the main source of complexity in the design and implementation of \CFA concurrency. 1584 1585 1586 \subsection{Barging Prevention} 1587 1588 Figure~\ref{f:BargingPrevention} shows \CFA code where bulk acquire adds complexity to the internal-signalling semantics. 1589 The complexity begins at the end of the inner @mutex@ statement, where the semantics of internal scheduling need to be extended for multiple monitors. 1590 The problem is that bulk acquire is used in the inner @mutex@ statement where one of the monitors is already acquired. 1591 When the signalling thread reaches the end of the inner @mutex@ statement, it should transfer ownership of @m1@ and @m2@ to the waiting thread to prevent barging into the outer @mutex@ statement by another thread. 1592 However, both the signalling and signalled threads still need monitor @m1@. 1593 1594 \begin{figure} 1595 \newbox\myboxA 1596 \begin{lrbox}{\myboxA} 1597 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1598 monitor M m1, m2; 1599 condition c; 1600 mutex( m1 ) { 1601 ... 1602 mutex( m1, m2 ) { 1603 ... `wait( c )`; // block and release m1, m2 1604 // m1, m2 acquired 1605 } // $\LstCommentStyle{\color{red}release m2}$ 1606 // m1 acquired 1607 } // release m1 1608 \end{cfa} 1609 \end{lrbox} 1610 1611 \newbox\myboxB 1612 \begin{lrbox}{\myboxB} 1613 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1614 1615 1616 mutex( m1 ) { 1617 ... 1618 mutex( m1, m2 ) { 1619 ... `signal( c )`; ... 1620 // m1, m2 acquired 1621 } // $\LstCommentStyle{\color{red}release m2}$ 1622 // m1 acquired 1623 } // release m1 1624 \end{cfa} 1625 \end{lrbox} 1626 1627 \newbox\myboxC 1628 \begin{lrbox}{\myboxC} 1629 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 1630 1631 1632 mutex( m1 ) { 1633 ... `wait( c )`; ... 1634 // m1 acquired 1635 } // $\LstCommentStyle{\color{red}release m1}$ 1636 1637 1638 1639 1640 \end{cfa} 1641 \end{lrbox} 1642 1643 \begin{cquote} 1644 \subfloat[Waiting Thread]{\label{f:WaitingThread}\usebox\myboxA} 1645 \hspace{2\parindentlnth} 1646 \subfloat[Signalling Thread]{\label{f:SignallingThread}\usebox\myboxB} 1647 \hspace{2\parindentlnth} 1648 \subfloat[Other Waiting Thread]{\label{f:SignallingThread}\usebox\myboxC} 1649 \end{cquote} 1650 \caption{Barging Prevention} 1651 \label{f:BargingPrevention} 1652 \end{figure} 1653 1654 The obvious solution to the problem of multi-monitor scheduling is to keep ownership of all locks until the last lock is ready to be transferred. 1655 It can be argued that that moment is when the last lock is no longer needed, because this semantics fits most closely to the behaviour of single-monitor scheduling. 1656 This solution has the main benefit of transferring ownership of groups of monitors, which simplifies the semantics from multiple objects to a single group of objects, effectively making the existing single-monitor semantic viable by simply changing monitors to monitor groups. 1657 This solution releases the monitors once every monitor in a group can be released. 1658 However, since some monitors are never released (\eg the monitor of a thread), this interpretation means a group might never be released. 1659 A more interesting interpretation is to transfer the group until all its monitors are released, which means the group is not passed further and a thread can retain its locks. 1660 1661 However, listing \ref{f:int-secret} shows this solution can become much more complicated depending on what is executed while secretly holding B at line \ref{line:secret}, while avoiding the need to transfer ownership of a subset of the condition monitors. 1662 Figure~\ref{f:dependency} shows a slightly different example where a third thread is waiting on monitor @A@, using a different condition variable. 1663 Because the third thread is signalled when secretly holding @B@, the goal becomes unreachable. 1664 Depending on the order of signals (listing \ref{f:dependency} line \ref{line:signal-ab} and \ref{line:signal-a}) two cases can happen: 1665 1666 \begin{comment} 1667 \paragraph{Case 1: thread $\alpha$ goes first.} In this case, the problem is that monitor @A@ needs to be passed to thread $\beta$ when thread $\alpha$ is done with it. 1668 \paragraph{Case 2: thread $\beta$ goes first.} In this case, the problem is that monitor @B@ needs to be retained and passed to thread $\alpha$ along with monitor @A@, which can be done directly or possibly using thread $\beta$ as an intermediate. 1669 \\ 1670 1671 Note that ordering is not determined by a race condition but by whether signalled threads are enqueued in FIFO or FILO order. 1672 However, regardless of the answer, users can move line \ref{line:signal-a} before line \ref{line:signal-ab} and get the reverse effect for listing \ref{f:dependency}. 1673 1674 In both cases, the threads need to be able to distinguish, on a per monitor basis, which ones need to be released and which ones need to be transferred, which means knowing when to release a group becomes complex and inefficient (see next section) and therefore effectively precludes this approach. 1675 1676 1677 \subsubsection{Dependency graphs} 1678 1679 \begin{figure} 1680 \begin{multicols}{3} 1681 Thread $\alpha$ 1682 \begin{cfa}[numbers=left, firstnumber=1] 1683 acquire A 1684 acquire A & B 1685 wait A & B 1686 release A & B 1687 release A 1688 \end{cfa} 1689 \columnbreak 1690 Thread $\gamma$ 1691 \begin{cfa}[numbers=left, firstnumber=6, escapechar=|] 1692 acquire A 1693 acquire A & B 1694 |\label{line:signal-ab}|signal A & B 1695 |\label{line:release-ab}|release A & B 1696 |\label{line:signal-a}|signal A 1697 |\label{line:release-a}|release A 1698 \end{cfa} 1699 \columnbreak 1700 Thread $\beta$ 1701 \begin{cfa}[numbers=left, firstnumber=12, escapechar=|] 1702 acquire A 1703 wait A 1704 |\label{line:release-aa}|release A 1705 \end{cfa} 1706 \end{multicols} 1707 \begin{cfa}[caption={Pseudo-code for the three thread example.},label={f:dependency}] 1708 \end{cfa} 1709 \begin{center} 1710 \input{dependency} 1711 \end{center} 1712 \caption{Dependency graph of the statements in listing \ref{f:dependency}} 1713 \label{fig:dependency} 1714 \end{figure} 1715 1716 In listing \ref{f:int-bulk-cfa}, there is a solution that satisfies both barging prevention and mutual exclusion. 1717 If ownership of both monitors is transferred to the waiter when the signaller releases @A & B@ and then the waiter transfers back ownership of @A@ back to the signaller when it releases it, then the problem is solved (@B@ is no longer in use at this point). 1718 Dynamically finding the correct order is therefore the second possible solution. 1719 The problem is effectively resolving a dependency graph of ownership requirements. 1720 Here even the simplest of code snippets requires two transfers and has a super-linear complexity. 1721 This complexity can be seen in listing \ref{f:explosion}, which is just a direct extension to three monitors, requires at least three ownership transfer and has multiple solutions. 1722 Furthermore, the presence of multiple solutions for ownership transfer can cause deadlock problems if a specific solution is not consistently picked; In the same way that multiple lock acquiring order can cause deadlocks. 1723 \begin{figure} 1724 \begin{multicols}{2} 1725 \begin{cfa} 1726 acquire A 1727 acquire B 1728 acquire C 1729 wait A & B & C 1730 release C 1731 release B 1732 release A 1733 \end{cfa} 1734 1735 \columnbreak 1736 1737 \begin{cfa} 1738 acquire A 1739 acquire B 1740 acquire C 1741 signal A & B & C 1742 release C 1743 release B 1744 release A 1745 \end{cfa} 1746 \end{multicols} 1747 \begin{cfa}[caption={Extension to three monitors of listing \ref{f:int-bulk-cfa}},label={f:explosion}] 1748 \end{cfa} 1749 \end{figure} 1750 1751 Given the three threads example in listing \ref{f:dependency}, figure \ref{fig:dependency} shows the corresponding dependency graph that results, where every node is a statement of one of the three threads, and the arrows the dependency of that statement (\eg $\alpha1$ must happen before $\alpha2$). 1752 The extra challenge is that this dependency graph is effectively post-mortem, but the runtime system needs to be able to build and solve these graphs as the dependencies unfold. 1753 Resolving dependency graphs being a complex and expensive endeavour, this solution is not the preferred one. 1754 1755 \subsubsection{Partial Signalling} \label{partial-sig} 1756 \end{comment} 1757 1758 Finally, the solution that is chosen for \CFA is to use partial signalling. 1759 Again using listing \ref{f:int-bulk-cfa}, the partial signalling solution transfers ownership of monitor @B@ at lines \ref{line:signal1} to the waiter but does not wake the waiting thread since it is still using monitor @A@. 1760 Only when it reaches line \ref{line:lastRelease} does it actually wake up the waiting thread. 1761 This solution has the benefit that complexity is encapsulated into only two actions: passing monitors to the next owner when they should be released and conditionally waking threads if all conditions are met. 1762 This solution has a much simpler implementation than a dependency graph solving algorithms, which is why it was chosen. 1763 Furthermore, after being fully implemented, this solution does not appear to have any significant downsides. 1764 1765 Using partial signalling, listing \ref{f:dependency} can be solved easily: 1766 \begin{itemize} 1767 \item When thread $\gamma$ reaches line \ref{line:release-ab} it transfers monitor @B@ to thread $\alpha$ and continues to hold monitor @A@. 1768 \item When thread $\gamma$ reaches line \ref{line:release-a} it transfers monitor @A@ to thread $\beta$ and wakes it up. 1769 \item When thread $\beta$ reaches line \ref{line:release-aa} it transfers monitor @A@ to thread $\alpha$ and wakes it up. 1770 \end{itemize} 1771 1772 1773 \subsection{Signalling: Now or Later} 2006 1774 2007 1775 \begin{figure} … … 2014 1782 int GirlPhNo, BoyPhNo; 2015 1783 condition Girls[CCodes], Boys[CCodes]; 2016 `condition exchange;`1784 condition exchange; 2017 1785 }; 2018 1786 int girl( DS & mutex ds, int phNo, int ccode ) { … … 2020 1788 wait( Girls[ccode] ); 2021 1789 GirlPhNo = phNo; 2022 `signal( exchange );`1790 exchange.signal(); 2023 1791 } else { 2024 1792 GirlPhNo = phNo; 2025 `signal( Boys[ccode] );`2026 `wait( exchange );`2027 } 1793 signal( Boys[ccode] ); 1794 exchange.wait(); 1795 } // if 2028 1796 return BoyPhNo; 2029 1797 } … … 2050 1818 } else { 2051 1819 GirlPhNo = phNo; // make phone number available 2052 `signal_block( Boys[ccode] );`// restart boy1820 signal_block( Boys[ccode] ); // restart boy 2053 1821 2054 1822 } // if … … 2064 1832 \qquad 2065 1833 \subfloat[\lstinline@signal_block@]{\label{f:DatingSignalBlock}\usebox\myboxB} 2066 \caption{Dating service }2067 \label{f:Dating Service}1834 \caption{Dating service. } 1835 \label{f:Dating service} 2068 1836 \end{figure} 2069 1837 2070 In summation, for internal scheduling, non-blocking signalling (as in the producer/consumer example) is used when the signaller is providing the cooperation for a waiting thread; 2071 the signaller enters the monitor and changes state, detects a waiting threads that can use the state, performs a non-blocking signal on the condition queue for the waiting thread, and exits the monitor to run concurrently. 2072 The waiter unblocks next from the urgent queue, uses/takes the state, and exits the monitor. 2073 Blocking signal is the reverse, where the waiter is providing the cooperation for the signalling thread; 2074 the signaller enters the monitor, detects a waiting thread providing the necessary state, performs a blocking signal to place it on the urgent queue and unblock the waiter. 2075 The waiter changes state and exits the monitor, and the signaller unblocks next from the urgent queue to use/take the state. 2076 2077 Both internal and external scheduling extend to multiple monitors in a natural way. 2078 \begin{cquote} 2079 \begin{tabular}{@{}l@{\hspace{3\parindentlnth}}l@{}} 2080 \begin{cfa} 2081 monitor M { `condition e`; ... }; 2082 void foo( M & mutex m1, M & mutex m2 ) { 2083 ... wait( `e` ); ... // wait( e, m1, m2 ) 2084 ... wait( `e, m1` ); ... 2085 ... wait( `e, m2` ); ... 2086 } 2087 \end{cfa} 2088 & 2089 \begin{cfa} 2090 void rtn$\(_1\)$( M & mutex m1, M & mutex m2 ); 2091 void rtn$\(_2\)$( M & mutex m1 ); 2092 void bar( M & mutex m1, M & mutex m2 ) { 2093 ... waitfor( `rtn` ); ... // $\LstCommentStyle{waitfor( rtn\(_1\), m1, m2 )}$ 2094 ... waitfor( `rtn, m1` ); ... // $\LstCommentStyle{waitfor( rtn\(_2\), m1 )}$ 2095 } 2096 \end{cfa} 1838 An important note is that, until now, signalling a monitor was a delayed operation. 1839 The ownership of the monitor is transferred only when the monitor would have otherwise been released, not at the point of the @signal@ statement. 1840 However, in some cases, it may be more convenient for users to immediately transfer ownership to the thread that is waiting for cooperation, which is achieved using the @signal_block@ routine. 1841 1842 The example in table \ref{tbl:datingservice} highlights the difference in behaviour. 1843 As mentioned, @signal@ only transfers ownership once the current critical section exits; this behaviour requires additional synchronization when a two-way handshake is needed. 1844 To avoid this explicit synchronization, the @condition@ type offers the @signal_block@ routine, which handles the two-way handshake as shown in the example. 1845 This feature removes the need for a second condition variables and simplifies programming. 1846 Like every other monitor semantic, @signal_block@ uses barging prevention, which means mutual-exclusion is baton-passed both on the front end and the back end of the call to @signal_block@, meaning no other thread can acquire the monitor either before or after the call. 1847 1848 % ====================================================================== 1849 % ====================================================================== 1850 \section{External scheduling} \label{extsched} 1851 % ====================================================================== 1852 % ====================================================================== 1853 An alternative to internal scheduling is external scheduling (see Table~\ref{tbl:sched}). 1854 1855 \begin{comment} 1856 \begin{table} 1857 \begin{tabular}{|c|c|c|} 1858 Internal Scheduling & External Scheduling & Go\\ 1859 \hline 1860 \begin{uC++}[tabsize=3] 1861 _Monitor Semaphore { 1862 condition c; 1863 bool inUse; 1864 public: 1865 void P() { 1866 if(inUse) 1867 wait(c); 1868 inUse = true; 1869 } 1870 void V() { 1871 inUse = false; 1872 signal(c); 1873 } 1874 } 1875 \end{uC++}&\begin{uC++}[tabsize=3] 1876 _Monitor Semaphore { 1877 1878 bool inUse; 1879 public: 1880 void P() { 1881 if(inUse) 1882 _Accept(V); 1883 inUse = true; 1884 } 1885 void V() { 1886 inUse = false; 1887 1888 } 1889 } 1890 \end{uC++}&\begin{Go}[tabsize=3] 1891 type MySem struct { 1892 inUse bool 1893 c chan bool 1894 } 1895 1896 // acquire 1897 func (s MySem) P() { 1898 if s.inUse { 1899 select { 1900 case <-s.c: 1901 } 1902 } 1903 s.inUse = true 1904 } 1905 1906 // release 1907 func (s MySem) V() { 1908 s.inUse = false 1909 1910 // This actually deadlocks 1911 // when single thread 1912 s.c <- false 1913 } 1914 \end{Go} 2097 1915 \end{tabular} 2098 \end{cquote} 2099 For @wait( e )@, the default semantics is to atomically block the signaller and release all acquired mutex parameters, \ie @wait( e, m1, m2 )@. 2100 To override the implicit multi-monitor wait, specific mutex parameter(s) can be specified, \eg @wait( e, m1 )@. 2101 Wait cannot statically verifies the released monitors are the acquired mutex-parameters without disallowing separately compiled helper functions calling @wait@. 2102 While \CC supports bulk locking, @wait@ only accepts a single lock for a condition variable, so bulk locking with condition variables is asymmetric. 2103 Finally, a signaller, 2104 \begin{cfa} 2105 void baz( M & mutex m1, M & mutex m2 ) { 2106 ... signal( e ); ... 2107 } 2108 \end{cfa} 2109 must have acquired at least the same locks as the waiting thread signalled from a condition queue to allow the locks to be passed, and hence, prevent barging. 2110 2111 Similarly, for @waitfor( rtn )@, the default semantics is to atomically block the acceptor and release all acquired mutex parameters, \ie @waitfor( rtn, m1, m2 )@. 2112 To override the implicit multi-monitor wait, specific mutex parameter(s) can be specified, \eg @waitfor( rtn, m1 )@. 2113 @waitfor@ does statically verify the monitor types passed are the same as the acquired mutex-parameters of the given function or function pointer, hence the function (pointer) prototype must be accessible. 2114 % When an overloaded function appears in an @waitfor@ statement, calls to any function with that name are accepted. 2115 % The rationale is that members with the same name should perform a similar function, and therefore, all should be eligible to accept a call. 2116 Overloaded functions can be disambiguated using a cast 2117 \begin{cfa} 2118 void rtn( M & mutex m ); 2119 `int` rtn( M & mutex m ); 2120 waitfor( (`int` (*)( M & mutex ))rtn, m ); 2121 \end{cfa} 2122 2123 The ability to release a subset of acquired monitors can result in a \newterm{nested monitor}~\cite{Lister77} deadlock. 2124 \begin{cfa} 2125 void foo( M & mutex m1, M & mutex m2 ) { 2126 ... wait( `e, m1` ); ... $\C{// release m1, keeping m2 acquired )}$ 2127 void bar( M & mutex m1, M & mutex m2 ) { $\C{// must acquire m1 and m2 )}$ 2128 ... signal( `e` ); ... 2129 \end{cfa} 2130 The @wait@ only releases @m1@ so the signalling thread cannot acquire @m1@ and @m2@ to enter @bar@ and @signal@ the condition. 2131 While deadlock can occur with multiple/nesting acquisition, this is a consequence of locks, and by extension monitors, not being perfectly composable. 2132 2133 2134 2135 \subsection{\texorpdfstring{Extended \protect\lstinline@waitfor@}{Extended waitfor}} 2136 2137 Figure~\ref{f:ExtendedWaitfor} shows the extended form of the @waitfor@ statement to conditionally accept one of a group of mutex functions, with an optional statement to be performed \emph{after} the mutex function finishes. 2138 For a @waitfor@ clause to be executed, its @when@ must be true and an outstanding call to its corresponding member(s) must exist. 2139 The \emph{conditional-expression} of a @when@ may call a function, but the function must not block or context switch. 2140 If there are multiple acceptable mutex calls, selection occurs top-to-bottom (prioritized) among the @waitfor@ clauses, whereas some programming languages with similar mechanisms accept nondeterministically for this case, \eg Go \lstinline[morekeywords=select]@select@. 2141 If some accept guards are true and there are no outstanding calls to these members, the acceptor is blocked until a call to one of these members is made. 2142 If there is a @timeout@ clause, it provides an upper bound on waiting. 2143 If all the accept guards are false, the statement does nothing, unless there is a terminating @else@ clause with a true guard, which is executed instead. 2144 Hence, the terminating @else@ clause allows a conditional attempt to accept a call without blocking. 2145 If both @timeout@ and @else@ clause are present, the @else@ must be conditional, or the @timeout@ is never triggered. 2146 There is also a traditional future wait queue (not shown) (\eg Microsoft (@WaitForMultipleObjects@)), to wait for a specified number of future elements in the queue. 1916 \caption{Different forms of scheduling.} 1917 \label{tbl:sched} 1918 \end{table} 1919 \end{comment} 1920 1921 This method is more constrained and explicit, which helps users reduce the non-deterministic nature of concurrency. 1922 Indeed, as the following examples demonstrate, external scheduling allows users to wait for events from other threads without the concern of unrelated events occurring. 1923 External scheduling can generally be done either in terms of control flow (\eg Ada with @accept@, \uC with @_Accept@) or in terms of data (\eg Go with channels). 1924 Of course, both of these paradigms have their own strengths and weaknesses, but for this project, control-flow semantics was chosen to stay consistent with the rest of the languages semantics. 1925 Two challenges specific to \CFA arise when trying to add external scheduling with loose object definitions and multiple-monitor routines. 1926 The previous example shows a simple use @_Accept@ versus @wait@/@signal@ and its advantages. 1927 Note that while other languages often use @accept@/@select@ as the core external scheduling keyword, \CFA uses @waitfor@ to prevent name collisions with existing socket \textbf{api}s. 1928 1929 For the @P@ member above using internal scheduling, the call to @wait@ only guarantees that @V@ is the last routine to access the monitor, allowing a third routine, say @isInUse()@, acquire mutual exclusion several times while routine @P@ is waiting. 1930 On the other hand, external scheduling guarantees that while routine @P@ is waiting, no other routine than @V@ can acquire the monitor. 1931 1932 % ====================================================================== 1933 % ====================================================================== 1934 \subsection{Loose Object Definitions} 1935 % ====================================================================== 1936 % ====================================================================== 1937 In \uC, a monitor class declaration includes an exhaustive list of monitor operations. 1938 Since \CFA is not object oriented, monitors become both more difficult to implement and less clear for a user: 1939 1940 \begin{cfa} 1941 monitor A {}; 1942 1943 void f(A & mutex a); 1944 void g(A & mutex a) { 1945 waitfor(f); // Obvious which f() to wait for 1946 } 1947 1948 void f(A & mutex a, int); // New different F added in scope 1949 void h(A & mutex a) { 1950 waitfor(f); // Less obvious which f() to wait for 1951 } 1952 \end{cfa} 1953 1954 Furthermore, external scheduling is an example where implementation constraints become visible from the interface. 1955 Here is the cfa-code for the entering phase of a monitor: 1956 \begin{center} 1957 \begin{tabular}{l} 1958 \begin{cfa} 1959 if monitor is free 1960 enter 1961 elif already own the monitor 1962 continue 1963 elif monitor accepts me 1964 enter 1965 else 1966 block 1967 \end{cfa} 1968 \end{tabular} 1969 \end{center} 1970 For the first two conditions, it is easy to implement a check that can evaluate the condition in a few instructions. 1971 However, a fast check for @monitor accepts me@ is much harder to implement depending on the constraints put on the monitors. 1972 Indeed, monitors are often expressed as an entry queue and some acceptor queue as in Figure~\ref{fig:ClassicalMonitor}. 2147 1973 2148 1974 \begin{figure} 2149 1975 \centering 2150 \begin{cfa} 2151 `when` ( $\emph{conditional-expression}$ ) $\C{// optional guard}$ 2152 waitfor( $\emph{mutex-member-name}$ ) $\emph{statement}$ $\C{// action after call}$ 2153 `or` `when` ( $\emph{conditional-expression}$ ) $\C{// any number of functions}$ 2154 waitfor( $\emph{mutex-member-name}$ ) $\emph{statement}$ 2155 `or` ... 2156 `when` ( $\emph{conditional-expression}$ ) $\C{// optional guard}$ 2157 `timeout` $\emph{statement}$ $\C{// optional terminating timeout clause}$ 2158 `when` ( $\emph{conditional-expression}$ ) $\C{// optional guard}$ 2159 `else` $\emph{statement}$ $\C{// optional terminating clause}$ 2160 \end{cfa} 2161 \caption{Extended \protect\lstinline@waitfor@} 2162 \label{f:ExtendedWaitfor} 1976 \subfloat[Classical Monitor] { 1977 \label{fig:ClassicalMonitor} 1978 {\resizebox{0.45\textwidth}{!}{\input{monitor}}} 1979 }% subfloat 1980 \qquad 1981 \subfloat[bulk acquire Monitor] { 1982 \label{fig:BulkMonitor} 1983 {\resizebox{0.45\textwidth}{!}{\input{ext_monitor}}} 1984 }% subfloat 1985 \caption{External Scheduling Monitor} 2163 1986 \end{figure} 2164 1987 2165 Note, a group of conditional @waitfor@ clauses is \emph{not} the same as a group of @if@ statements, \eg: 2166 \begin{cfa} 2167 if ( C1 ) waitfor( mem1 ); when ( C1 ) waitfor( mem1 ); 2168 else if ( C2 ) waitfor( mem2 ); or when ( C2 ) waitfor( mem2 ); 2169 \end{cfa} 2170 The left example only accepts @mem1@ if @C1@ is true or only @mem2@ if @C2@ is true. 2171 The right example accepts either @mem1@ or @mem2@ if @C1@ and @C2@ are true. 2172 2173 An interesting use of @waitfor@ is accepting the @mutex@ destructor to know when an object is deallocated, \eg assume the bounded buffer is restructred from a monitor to a thread with the following @main@. 2174 \begin{cfa} 2175 void main( Buffer(T) & buffer ) with(buffer) { 2176 for () { 2177 `waitfor( ^?{}, buffer )` break; 2178 or when ( count != 20 ) waitfor( insert, buffer ) { ... } 2179 or when ( count != 0 ) waitfor( remove, buffer ) { ... } 1988 There are other alternatives to these pictures, but in the case of the left picture, implementing a fast accept check is relatively easy. 1989 Restricted to a fixed number of mutex members, N, the accept check reduces to updating a bitmask when the acceptor queue changes, a check that executes in a single instruction even with a fairly large number (\eg 128) of mutex members. 1990 This approach requires a unique dense ordering of routines with an upper-bound and that ordering must be consistent across translation units. 1991 For OO languages these constraints are common, since objects only offer adding member routines consistently across translation units via inheritance. 1992 However, in \CFA users can extend objects with mutex routines that are only visible in certain translation unit. 1993 This means that establishing a program-wide dense-ordering among mutex routines can only be done in the program linking phase, and still could have issues when using dynamically shared objects. 1994 1995 The alternative is to alter the implementation as in Figure~\ref{fig:BulkMonitor}. 1996 Here, the mutex routine called is associated with a thread on the entry queue while a list of acceptable routines is kept separate. 1997 Generating a mask dynamically means that the storage for the mask information can vary between calls to @waitfor@, allowing for more flexibility and extensions. 1998 Storing an array of accepted routine pointers replaces the single instruction bitmask comparison with dereferencing a pointer followed by a linear search. 1999 Furthermore, supporting nested external scheduling (\eg listing \ref{f:nest-ext}) may now require additional searches for the @waitfor@ statement to check if a routine is already queued. 2000 2001 \begin{figure} 2002 \begin{cfa}[caption={Example of nested external scheduling},label={f:nest-ext}] 2003 monitor M {}; 2004 void foo( M & mutex a ) {} 2005 void bar( M & mutex b ) { 2006 // Nested in the waitfor(bar, c) call 2007 waitfor(foo, b); 2008 } 2009 void baz( M & mutex c ) { 2010 waitfor(bar, c); 2011 } 2012 2013 \end{cfa} 2014 \end{figure} 2015 2016 Note that in the right picture, tasks need to always keep track of the monitors associated with mutex routines, and the routine mask needs to have both a routine pointer and a set of monitors, as is discussed in the next section. 2017 These details are omitted from the picture for the sake of simplicity. 2018 2019 At this point, a decision must be made between flexibility and performance. 2020 Many design decisions in \CFA achieve both flexibility and performance, for example polymorphic routines add significant flexibility but inlining them means the optimizer can easily remove any runtime cost. 2021 Here, however, the cost of flexibility cannot be trivially removed. 2022 In the end, the most flexible approach has been chosen since it allows users to write programs that would otherwise be hard to write. 2023 This decision is based on the assumption that writing fast but inflexible locks is closer to a solved problem than writing locks that are as flexible as external scheduling in \CFA. 2024 2025 % ====================================================================== 2026 % ====================================================================== 2027 \subsection{Multi-Monitor Scheduling} 2028 % ====================================================================== 2029 % ====================================================================== 2030 2031 External scheduling, like internal scheduling, becomes significantly more complex when introducing multi-monitor syntax. 2032 Even in the simplest possible case, some new semantics needs to be established: 2033 \begin{cfa} 2034 monitor M {}; 2035 2036 void f(M & mutex a); 2037 2038 void g(M & mutex b, M & mutex c) { 2039 waitfor(f); // two monitors M => unknown which to pass to f(M & mutex) 2040 } 2041 \end{cfa} 2042 The obvious solution is to specify the correct monitor as follows: 2043 2044 \begin{cfa} 2045 monitor M {}; 2046 2047 void f(M & mutex a); 2048 2049 void g(M & mutex a, M & mutex b) { 2050 // wait for call to f with argument b 2051 waitfor(f, b); 2052 } 2053 \end{cfa} 2054 This syntax is unambiguous. 2055 Both locks are acquired and kept by @g@. 2056 When routine @f@ is called, the lock for monitor @b@ is temporarily transferred from @g@ to @f@ (while @g@ still holds lock @a@). 2057 This behaviour can be extended to the multi-monitor @waitfor@ statement as follows. 2058 2059 \begin{cfa} 2060 monitor M {}; 2061 2062 void f(M & mutex a, M & mutex b); 2063 2064 void g(M & mutex a, M & mutex b) { 2065 // wait for call to f with arguments a and b 2066 waitfor(f, a, b); 2067 } 2068 \end{cfa} 2069 2070 Note that the set of monitors passed to the @waitfor@ statement must be entirely contained in the set of monitors already acquired in the routine. @waitfor@ used in any other context is undefined behaviour. 2071 2072 An important behaviour to note is when a set of monitors only match partially: 2073 2074 \begin{cfa} 2075 mutex struct A {}; 2076 2077 mutex struct B {}; 2078 2079 void g(A & mutex a, B & mutex b) { 2080 waitfor(f, a, b); 2081 } 2082 2083 A a1, a2; 2084 B b; 2085 2086 void foo() { 2087 g(a1, b); // block on accept 2088 } 2089 2090 void bar() { 2091 f(a2, b); // fulfill cooperation 2092 } 2093 \end{cfa} 2094 While the equivalent can happen when using internal scheduling, the fact that conditions are specific to a set of monitors means that users have to use two different condition variables. 2095 In both cases, partially matching monitor sets does not wakeup the waiting thread. 2096 It is also important to note that in the case of external scheduling the order of parameters is irrelevant; @waitfor(f,a,b)@ and @waitfor(f,b,a)@ are indistinguishable waiting condition. 2097 2098 % ====================================================================== 2099 % ====================================================================== 2100 \subsection{\protect\lstinline|waitfor| Semantics} 2101 % ====================================================================== 2102 % ====================================================================== 2103 2104 Syntactically, the @waitfor@ statement takes a routine identifier and a set of monitors. 2105 While the set of monitors can be any list of expressions, the routine name is more restricted because the compiler validates at compile time the validity of the routine type and the parameters used with the @waitfor@ statement. 2106 It checks that the set of monitors passed in matches the requirements for a routine call. 2107 Figure~\ref{f:waitfor} shows various usages of the waitfor statement and which are acceptable. 2108 The choice of the routine type is made ignoring any non-@mutex@ parameter. 2109 One limitation of the current implementation is that it does not handle overloading, but overloading is possible. 2110 \begin{figure} 2111 \begin{cfa}[caption={Various correct and incorrect uses of the waitfor statement},label={f:waitfor}] 2112 monitor A{}; 2113 monitor B{}; 2114 2115 void f1( A & mutex ); 2116 void f2( A & mutex, B & mutex ); 2117 void f3( A & mutex, int ); 2118 void f4( A & mutex, int ); 2119 void f4( A & mutex, double ); 2120 2121 void foo( A & mutex a1, A & mutex a2, B & mutex b1, B & b2 ) { 2122 A * ap = & a1; 2123 void (*fp)( A & mutex ) = f1; 2124 2125 waitfor(f1, a1); // Correct : 1 monitor case 2126 waitfor(f2, a1, b1); // Correct : 2 monitor case 2127 waitfor(f3, a1); // Correct : non-mutex arguments are ignored 2128 waitfor(f1, *ap); // Correct : expression as argument 2129 2130 waitfor(f1, a1, b1); // Incorrect : Too many mutex arguments 2131 waitfor(f2, a1); // Incorrect : Too few mutex arguments 2132 waitfor(f2, a1, a2); // Incorrect : Mutex arguments don't match 2133 waitfor(f1, 1); // Incorrect : 1 not a mutex argument 2134 waitfor(f9, a1); // Incorrect : f9 routine does not exist 2135 waitfor(*fp, a1 ); // Incorrect : fp not an identifier 2136 waitfor(f4, a1); // Incorrect : f4 ambiguous 2137 2138 waitfor(f2, a1, b2); // Undefined behaviour : b2 not mutex 2139 } 2140 \end{cfa} 2141 \end{figure} 2142 2143 Finally, for added flexibility, \CFA supports constructing a complex @waitfor@ statement using the @or@, @timeout@ and @else@. 2144 Indeed, multiple @waitfor@ clauses can be chained together using @or@; this chain forms a single statement that uses baton pass to any routine that fits one of the routine+monitor set passed in. 2145 To enable users to tell which accepted routine executed, @waitfor@s are followed by a statement (including the null statement @;@) or a compound statement, which is executed after the clause is triggered. 2146 A @waitfor@ chain can also be followed by a @timeout@, to signify an upper bound on the wait, or an @else@, to signify that the call should be non-blocking, which checks for a matching routine call already arrived and otherwise continues. 2147 Any and all of these clauses can be preceded by a @when@ condition to dynamically toggle the accept clauses on or off based on some current state. 2148 Figure~\ref{f:waitfor2} demonstrates several complex masks and some incorrect ones. 2149 2150 \begin{figure} 2151 \lstset{language=CFA,deletedelim=**[is][]{`}{`}} 2152 \begin{cfa} 2153 monitor A{}; 2154 2155 void f1( A & mutex ); 2156 void f2( A & mutex ); 2157 2158 void foo( A & mutex a, bool b, int t ) { 2159 waitfor(f1, a); $\C{// Correct : blocking case}$ 2160 2161 waitfor(f1, a) { $\C{// Correct : block with statement}$ 2162 sout | "f1" | endl; 2180 2163 } 2181 // clean up 2182 } 2183 \end{cfa} 2184 When the program main deallocates the buffer, it first calls the buffer's destructor, which is accepted, the destructor runs, and the buffer is deallocated. 2185 However, the buffer thread cannot continue after the destructor call because the object is gone; 2186 hence, clean up in @main@ cannot occur, which means destructors for local objects are not run. 2187 To make this useful capability work, the semantics for accepting the destructor is the same as @signal@, \ie the destructor call is placed on urgent and the acceptor continues execution, which ends the loop, cleans up, and the thread terminates. 2188 Then, the destructor caller unblocks from urgent to deallocate the object. 2189 Accepting the destructor is the idiomatic way in \CFA to terminate a thread performing direct communication. 2190 2191 2192 \subsection{Bulk Barging Prevention} 2193 2194 Figure~\ref{f:BulkBargingPrevention} shows \CFA code where bulk acquire adds complexity to the internal-signalling semantics. 2195 The complexity begins at the end of the inner @mutex@ statement, where the semantics of internal scheduling need to be extended for multiple monitors. 2196 The problem is that bulk acquire is used in the inner @mutex@ statement where one of the monitors is already acquired. 2197 When the signalling thread reaches the end of the inner @mutex@ statement, it should transfer ownership of @m1@ and @m2@ to the waiting threads to prevent barging into the outer @mutex@ statement by another thread. 2198 However, both the signalling and waiting threads W1 and W2 need some subset of monitors @m1@ and @m2@. 2199 \begin{cquote} 2200 condition c: (order 1) W2(@m2@), W1(@m1@,@m2@)\ \ \ or\ \ \ (order 2) W1(@m1@,@m2@), W2(@m2@) \\ 2201 S: acq. @m1@ $\rightarrow$ acq. @m1,m2@ $\rightarrow$ @signal(c)@ $\rightarrow$ rel. @m2@ $\rightarrow$ pass @m2@ unblock W2 (order 2) $\rightarrow$ rel. @m1@ $\rightarrow$ pass @m1,m2@ unblock W1 \\ 2202 \hspace*{2.75in}$\rightarrow$ rel. @m1@ $\rightarrow$ pass @m1,m2@ unblock W1 (order 1) 2203 \end{cquote} 2204 2164 waitfor(f1, a) { $\C{// Correct : block waiting for f1 or f2}$ 2165 sout | "f1" | endl; 2166 } or waitfor(f2, a) { 2167 sout | "f2" | endl; 2168 } 2169 waitfor(f1, a); or else; $\C{// Correct : non-blocking case}$ 2170 2171 waitfor(f1, a) { $\C{// Correct : non-blocking case}$ 2172 sout | "blocked" | endl; 2173 } or else { 2174 sout | "didn't block" | endl; 2175 } 2176 waitfor(f1, a) { $\C{// Correct : block at most 10 seconds}$ 2177 sout | "blocked" | endl; 2178 } or timeout( 10`s) { 2179 sout | "didn't block" | endl; 2180 } 2181 // Correct : block only if b == true if b == false, don't even make the call 2182 when(b) waitfor(f1, a); 2183 2184 // Correct : block only if b == true if b == false, make non-blocking call 2185 waitfor(f1, a); or when(!b) else; 2186 2187 // Correct : block only of t > 1 2188 waitfor(f1, a); or when(t > 1) timeout(t); or else; 2189 2190 // Incorrect : timeout clause is dead code 2191 waitfor(f1, a); or timeout(t); or else; 2192 2193 // Incorrect : order must be waitfor [or waitfor... [or timeout] [or else]] 2194 timeout(t); or waitfor(f1, a); or else; 2195 } 2196 \end{cfa} 2197 \caption{Correct and incorrect uses of the or, else, and timeout clause around a waitfor statement} 2198 \label{f:waitfor2} 2199 \end{figure} 2200 2201 % ====================================================================== 2202 % ====================================================================== 2203 \subsection{Waiting For The Destructor} 2204 % ====================================================================== 2205 % ====================================================================== 2206 An interesting use for the @waitfor@ statement is destructor semantics. 2207 Indeed, the @waitfor@ statement can accept any @mutex@ routine, which includes the destructor (see section \ref{data}). 2208 However, with the semantics discussed until now, waiting for the destructor does not make any sense, since using an object after its destructor is called is undefined behaviour. 2209 The simplest approach is to disallow @waitfor@ on a destructor. 2210 However, a more expressive approach is to flip ordering of execution when waiting for the destructor, meaning that waiting for the destructor allows the destructor to run after the current @mutex@ routine, similarly to how a condition is signalled. 2205 2211 \begin{figure} 2206 \newbox\myboxA 2207 \begin{lrbox}{\myboxA} 2208 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2209 monitor M m1, m2; 2210 condition c; 2211 mutex( m1 ) { // $\LstCommentStyle{\color{red}outer}$ 2212 ... 2213 mutex( m1, m2 ) { // $\LstCommentStyle{\color{red}inner}$ 2214 ... `signal( c )`; ... 2215 // m1, m2 still acquired 2216 } // $\LstCommentStyle{\color{red}release m2}$ 2217 // m1 acquired 2218 } // release m1 2219 \end{cfa} 2220 \end{lrbox} 2221 2222 \newbox\myboxB 2223 \begin{lrbox}{\myboxB} 2224 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2225 2226 2227 mutex( m1 ) { 2228 ... 2229 mutex( m1, m2 ) { 2230 ... `wait( c )`; // release m1, m2 2231 // m1, m2 reacquired 2232 } // $\LstCommentStyle{\color{red}release m2}$ 2233 // m1 acquired 2234 } // release m1 2235 \end{cfa} 2236 \end{lrbox} 2237 2238 \newbox\myboxC 2239 \begin{lrbox}{\myboxC} 2240 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2241 2242 2243 mutex( m2 ) { 2244 ... `wait( c )`; // release m2 2245 // m2 reacquired 2246 } // $\LstCommentStyle{\color{red}release m2}$ 2247 2248 2249 2250 2251 \end{cfa} 2252 \end{lrbox} 2253 2254 \begin{cquote} 2255 \subfloat[Signalling Thread (S)]{\label{f:SignallingThread}\usebox\myboxA} 2256 \hspace{3\parindentlnth} 2257 \subfloat[Waiting Thread (W1)]{\label{f:WaitingThread}\usebox\myboxB} 2258 \hspace{2\parindentlnth} 2259 \subfloat[Waiting Thread (W2)]{\label{f:OtherWaitingThread}\usebox\myboxC} 2260 \end{cquote} 2261 \caption{Bulk Barging Prevention} 2262 \label{f:BulkBargingPrevention} 2212 \begin{cfa}[caption={Example of an executor which executes action in series until the destructor is called.},label={f:dtor-order}] 2213 monitor Executer {}; 2214 struct Action; 2215 2216 void ^?{} (Executer & mutex this); 2217 void execute(Executer & mutex this, const Action & ); 2218 void run (Executer & mutex this) { 2219 while(true) { 2220 waitfor(execute, this); 2221 or waitfor(^?{} , this) { 2222 break; 2223 } 2224 } 2225 } 2226 \end{cfa} 2263 2227 \end{figure} 2264 2265 One scheduling solution is for the signaller S to keep ownership of all locks until the last lock is ready to be transferred, because this semantics fits most closely to the behaviour of single-monitor scheduling. 2266 However, this solution is inefficient if W2 waited first and can be immediate passed @m2@ when released, while S retains @m1@ until completion of the outer mutex statement. 2267 If W1 waited first, the signaller must retain @m1@ amd @m2@ until completion of the outer mutex statement and then pass both to W1. 2268 % Furthermore, there is an execution sequence where the signaller always finds waiter W2, and hence, waiter W1 starves. 2269 To support this efficient semantics (and prevent barging), the implementation maintains a list of monitors acquired for each blocked thread. 2270 When a signaller exits or waits in a monitor function/statement, the front waiter on urgent is unblocked if all its monitors are released. 2271 Implementing a fast subset check for the necessary released monitors is important. 2272 % The benefit is encapsulating complexity into only two actions: passing monitors to the next owner when they should be released and conditionally waking threads if all conditions are met. 2273 2274 2275 \subsection{Loose Object Definitions} 2276 \label{s:LooseObjectDefinitions} 2277 2278 In an object-oriented programming language, a class includes an exhaustive list of operations. 2279 A new class can add members via static inheritance but the subclass still has an exhaustive list of operations. 2280 (Dynamic member adding, \eg JavaScript~\cite{JavaScript}, is not considered.) 2281 In the object-oriented scenario, the type and all its operators are always present at compilation (even separate compilation), so it is possible to number the operations in a bit mask and use an $O(1)$ compare with a similar bit mask created for the operations specified in a @waitfor@. 2282 2283 However, in \CFA, monitor functions can be statically added/removed in translation units, making a fast subset check difficult. 2284 \begin{cfa} 2285 monitor M { ... }; // common type, included in .h file 2286 translation unit 1 2287 void `f`( M & mutex m ); 2288 void g( M & mutex m ) { waitfor( `f`, m ); } 2289 translation unit 2 2290 void `f`( M & mutex m ); $\C{// replacing f and g for type M in this translation unit}$ 2291 void `g`( M & mutex m ); 2292 void h( M & mutex m ) { waitfor( `f`, m ) or waitfor( `g`, m ); } $\C{// extending type M in this translation unit}$ 2293 \end{cfa} 2294 The @waitfor@ statements in each translation unit cannot form a unique bit-mask because the monitor type does not carry that information. 2295 Hence, function pointers are used to identify the functions listed in the @waitfor@ statement, stored in a variable-sized array. 2296 Then, the same implementation approach used for the urgent stack is used for the calling queue. 2297 Each caller has a list of monitors acquired, and the @waitfor@ statement performs a (usually short) linear search matching functions in the @waitfor@ list with called functions, and then verifying the associated mutex locks can be transfers. 2298 (A possible way to construct a dense mapping is at link or load-time.) 2299 2300 2301 \subsection{Multi-Monitor Scheduling} 2302 \label{s:Multi-MonitorScheduling} 2303 2304 External scheduling, like internal scheduling, becomes significantly more complex for multi-monitor semantics. 2305 Even in the simplest case, new semantics need to be established. 2306 \begin{cfa} 2307 monitor M { ... }; 2308 void f( M & mutex m1 ); 2309 void g( M & mutex m1, M & mutex m2 ) { `waitfor( f );` } $\C{// pass m1 or m2 to f?}$ 2310 \end{cfa} 2311 The solution is for the programmer to disambiguate: 2312 \begin{cfa} 2313 waitfor( f, `m2` ); $\C{// wait for call to f with argument m2}$ 2314 \end{cfa} 2315 Both locks are acquired by function @g@, so when function @f@ is called, the lock for monitor @m2@ is passed from @g@ to @f@, while @g@ still holds lock @m1@. 2316 This behaviour can be extended to the multi-monitor @waitfor@ statement. 2317 \begin{cfa} 2318 monitor M { ... }; 2319 void f( M & mutex m1, M & mutex m2 ); 2320 void g( M & mutex m1, M & mutex m2 ) { waitfor( f, `m1, m2` ); $\C{// wait for call to f with arguments m1 and m2}$ 2321 \end{cfa} 2322 Again, the set of monitors passed to the @waitfor@ statement must be entirely contained in the set of monitors already acquired by the accepting function. 2323 Also, the order of the monitors in a @waitfor@ statement is unimportant. 2324 2325 Figure~\ref{f:UnmatchedMutexSets} shows an example where, for internal and external scheduling with multiple monitors, a signalling or accepting thread must match exactly, \ie partial matching results in waiting. 2326 For both examples, the set of monitors is disjoint so unblocking is impossible. 2327 2228 For example, listing \ref{f:dtor-order} shows an example of an executor with an infinite loop, which waits for the destructor to break out of this loop. 2229 Switching the semantic meaning introduces an idiomatic way to terminate a task and/or wait for its termination via destruction. 2230 2231 2232 % ###### # ###### # # # ####### # ### ##### # # 2233 % # # # # # # # # # # # # # # # ## ## 2234 % # # # # # # # # # # # # # # # # # # 2235 % ###### # # ###### # # # # ##### # # ##### # # # 2236 % # ####### # # ####### # # # # # # # # 2237 % # # # # # # # # # # # # # # # # 2238 % # # # # # # # ####### ####### ####### ####### ### ##### # # 2239 \section{Parallelism} 2240 Historically, computer performance was about processor speeds and instruction counts. 2241 However, with heat dissipation being a direct consequence of speed increase, parallelism has become the new source for increased performance~\cite{Sutter05, Sutter05b}. 2242 In this decade, it is no longer reasonable to create a high-performance application without caring about parallelism. 2243 Indeed, parallelism is an important aspect of performance and more specifically throughput and hardware utilization. 2244 The lowest-level approach of parallelism is to use \textbf{kthread} in combination with semantics like @fork@, @join@, \etc. 2245 However, since these have significant costs and limitations, \textbf{kthread} are now mostly used as an implementation tool rather than a user oriented one. 2246 There are several alternatives to solve these issues that all have strengths and weaknesses. 2247 While there are many variations of the presented paradigms, most of these variations do not actually change the guarantees or the semantics, they simply move costs in order to achieve better performance for certain workloads. 2248 2249 \section{Paradigms} 2250 \subsection{User-Level Threads} 2251 A direct improvement on the \textbf{kthread} approach is to use \textbf{uthread}. 2252 These threads offer most of the same features that the operating system already provides but can be used on a much larger scale. 2253 This approach is the most powerful solution as it allows all the features of multithreading, while removing several of the more expensive costs of kernel threads. 2254 The downside is that almost none of the low-level threading problems are hidden; users still have to think about data races, deadlocks and synchronization issues. 2255 These issues can be somewhat alleviated by a concurrency toolkit with strong guarantees, but the parallelism toolkit offers very little to reduce complexity in itself. 2256 2257 Examples of languages that support \textbf{uthread} are Erlang~\cite{Erlang} and \uC~\cite{uC++book}. 2258 2259 \subsection{Fibers : User-Level Threads Without Preemption} \label{fibers} 2260 A popular variant of \textbf{uthread} is what is often referred to as \textbf{fiber}. 2261 However, \textbf{fiber} do not present meaningful semantic differences with \textbf{uthread}. 2262 The significant difference between \textbf{uthread} and \textbf{fiber} is the lack of \textbf{preemption} in the latter. 2263 Advocates of \textbf{fiber} list their high performance and ease of implementation as major strengths, but the performance difference between \textbf{uthread} and \textbf{fiber} is controversial, and the ease of implementation, while true, is a weak argument in the context of language design. 2264 Therefore this proposal largely ignores fibers. 2265 2266 An example of a language that uses fibers is Go~\cite{Go} 2267 2268 \subsection{Jobs and Thread Pools} 2269 An approach on the opposite end of the spectrum is to base parallelism on \textbf{pool}. 2270 Indeed, \textbf{pool} offer limited flexibility but at the benefit of a simpler user interface. 2271 In \textbf{pool} based systems, users express parallelism as units of work, called jobs, and a dependency graph (either explicit or implicit) that ties them together. 2272 This approach means users need not worry about concurrency but significantly limit the interaction that can occur among jobs. 2273 Indeed, any \textbf{job} that blocks also block the underlying worker, which effectively means the CPU utilization, and therefore throughput, suffers noticeably. 2274 It can be argued that a solution to this problem is to use more workers than available cores. 2275 However, unless the number of jobs and the number of workers are comparable, having a significant number of blocked jobs always results in idles cores. 2276 2277 The gold standard of this implementation is Intel's TBB library~\cite{TBB}. 2278 2279 \subsection{Paradigm Performance} 2280 While the choice between the three paradigms listed above may have significant performance implications, it is difficult to pin down the performance implications of choosing a model at the language level. 2281 Indeed, in many situations one of these paradigms may show better performance but it all strongly depends on the workload. 2282 Having a large amount of mostly independent units of work to execute almost guarantees equivalent performance across paradigms and that the \textbf{pool}-based system has the best efficiency thanks to the lower memory overhead (\ie no thread stack per job). 2283 However, interactions among jobs can easily exacerbate contention. 2284 User-level threads allow fine-grain context switching, which results in better resource utilization, but a context switch is more expensive and the extra control means users need to tweak more variables to get the desired performance. 2285 Finally, if the units of uninterrupted work are large, enough the paradigm choice is largely amortized by the actual work done. 2286 2287 \section{The \protect\CFA\ Kernel : Processors, Clusters and Threads}\label{kernel} 2288 A \textbf{cfacluster} is a group of \textbf{kthread} executed in isolation. \textbf{uthread} are scheduled on the \textbf{kthread} of a given \textbf{cfacluster}, allowing organization between \textbf{uthread} and \textbf{kthread}. 2289 It is important that \textbf{kthread} belonging to a same \textbf{cfacluster} have homogeneous settings, otherwise migrating a \textbf{uthread} from one \textbf{kthread} to the other can cause issues. 2290 A \textbf{cfacluster} also offers a pluggable scheduler that can optimize the workload generated by the \textbf{uthread}. 2291 2292 \textbf{cfacluster} have not been fully implemented in the context of this paper. 2293 Currently \CFA only supports one \textbf{cfacluster}, the initial one. 2294 2295 \subsection{Future Work: Machine Setup}\label{machine} 2296 While this was not done in the context of this paper, another important aspect of clusters is affinity. 2297 While many common desktop and laptop PCs have homogeneous CPUs, other devices often have more heterogeneous setups. 2298 For example, a system using \textbf{numa} configurations may benefit from users being able to tie clusters and/or kernel threads to certain CPU cores. 2299 OS support for CPU affinity is now common~\cite{affinityLinux, affinityWindows, affinityFreebsd, affinityNetbsd, affinityMacosx}, which means it is both possible and desirable for \CFA to offer an abstraction mechanism for portable CPU affinity. 2300 2301 \subsection{Paradigms}\label{cfaparadigms} 2302 Given these building blocks, it is possible to reproduce all three of the popular paradigms. 2303 Indeed, \textbf{uthread} is the default paradigm in \CFA. 2304 However, disabling \textbf{preemption} on a cluster means threads effectively become fibers. 2305 Since several \textbf{cfacluster} with different scheduling policy can coexist in the same application, this allows \textbf{fiber} and \textbf{uthread} to coexist in the runtime of an application. 2306 Finally, it is possible to build executors for thread pools from \textbf{uthread} or \textbf{fiber}, which includes specialized jobs like actors~\cite{Actors}. 2307 2308 2309 2310 \section{Behind the Scenes} 2311 There are several challenges specific to \CFA when implementing concurrency. 2312 These challenges are a direct result of bulk acquire and loose object definitions. 2313 These two constraints are the root cause of most design decisions in the implementation. 2314 Furthermore, to avoid contention from dynamically allocating memory in a concurrent environment, the internal-scheduling design is (almost) entirely free of mallocs. 2315 This approach avoids the chicken and egg problem~\cite{Chicken} of having a memory allocator that relies on the threading system and a threading system that relies on the runtime. 2316 This extra goal means that memory management is a constant concern in the design of the system. 2317 2318 The main memory concern for concurrency is queues. 2319 All blocking operations are made by parking threads onto queues and all queues are designed with intrusive nodes, where each node has pre-allocated link fields for chaining, to avoid the need for memory allocation. 2320 Since several concurrency operations can use an unbound amount of memory (depending on bulk acquire), statically defining information in the intrusive fields of threads is insufficient.The only way to use a variable amount of memory without requiring memory allocation is to pre-allocate large buffers of memory eagerly and store the information in these buffers. 2321 Conveniently, the call stack fits that description and is easy to use, which is why it is used heavily in the implementation of internal scheduling, particularly variable-length arrays. 2322 Since stack allocation is based on scopes, the first step of the implementation is to identify the scopes that are available to store the information, and which of these can have a variable-length array. 2323 The threads and the condition both have a fixed amount of memory, while @mutex@ routines and blocking calls allow for an unbound amount, within the stack size. 2324 2325 Note that since the major contributions of this paper are extending monitor semantics to bulk acquire and loose object definitions, any challenges that are not resulting of these characteristics of \CFA are considered as solved problems and therefore not discussed. 2326 2327 % ====================================================================== 2328 % ====================================================================== 2329 \section{Mutex Routines} 2330 % ====================================================================== 2331 % ====================================================================== 2332 2333 The first step towards the monitor implementation is simple @mutex@ routines. 2334 In the single monitor case, mutual-exclusion is done using the entry/exit procedure in listing \ref{f:entry1}. 2335 The entry/exit procedures do not have to be extended to support multiple monitors. 2336 Indeed it is sufficient to enter/leave monitors one-by-one as long as the order is correct to prevent deadlock~\cite{Havender68}. 2337 In \CFA, ordering of monitor acquisition relies on memory ordering. 2338 This approach is sufficient because all objects are guaranteed to have distinct non-overlapping memory layouts and mutual-exclusion for a monitor is only defined for its lifetime, meaning that destroying a monitor while it is acquired is undefined behaviour. 2339 When a mutex call is made, the concerned monitors are aggregated into a variable-length pointer array and sorted based on pointer values. 2340 This array persists for the entire duration of the mutual-exclusion and its ordering reused extensively. 2328 2341 \begin{figure} 2329 \centering 2330 \begin{lrbox}{\myboxA} 2331 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2332 monitor M1 {} m11, m12; 2333 monitor M2 {} m2; 2334 condition c; 2335 void f( M1 & mutex m1, M2 & mutex m2 ) { 2336 signal( c ); 2337 } 2338 void g( M1 & mutex m1, M2 & mutex m2 ) { 2339 wait( c ); 2340 } 2341 g( `m11`, m2 ); // block on wait 2342 f( `m12`, m2 ); // cannot fulfil 2343 \end{cfa} 2344 \end{lrbox} 2345 2346 \begin{lrbox}{\myboxB} 2347 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2348 monitor M1 {} m11, m12; 2349 monitor M2 {} m2; 2350 2351 void f( M1 & mutex m1, M2 & mutex m2 ) { 2352 2353 } 2354 void g( M1 & mutex m1, M2 & mutex m2 ) { 2355 waitfor( f, m1, m2 ); 2356 } 2357 g( `m11`, m2 ); // block on accept 2358 f( `m12`, m2 ); // cannot fulfil 2359 \end{cfa} 2360 \end{lrbox} 2361 \subfloat[Internal scheduling]{\label{f:InternalScheduling}\usebox\myboxA} 2362 \hspace{3pt} 2363 \vrule 2364 \hspace{3pt} 2365 \subfloat[External scheduling]{\label{f:ExternalScheduling}\usebox\myboxB} 2366 \caption{Unmatched \protect\lstinline@mutex@ sets} 2367 \label{f:UnmatchedMutexSets} 2342 \begin{multicols}{2} 2343 Entry 2344 \begin{cfa} 2345 if monitor is free 2346 enter 2347 elif already own the monitor 2348 continue 2349 else 2350 block 2351 increment recursions 2352 \end{cfa} 2353 \columnbreak 2354 Exit 2355 \begin{cfa} 2356 decrement recursion 2357 if recursion == 0 2358 if entry queue not empty 2359 wake-up thread 2360 \end{cfa} 2361 \end{multicols} 2362 \begin{cfa}[caption={Initial entry and exit routine for monitors},label={f:entry1}] 2363 \end{cfa} 2368 2364 \end{figure} 2369 2365 2370 2371 \subsection{\texorpdfstring{\protect\lstinline@mutex@ Threads}{mutex Threads}} 2372 2373 Threads in \CFA can also be monitors to allow \emph{direct communication} among threads, \ie threads can have mutex functions that are called by other threads. 2374 Hence, all monitor features are available when using threads. 2375 Figure~\ref{f:DirectCommunication} shows a comparison of direct call communication in \CFA with direct channel communication in Go. 2376 (Ada provides a similar mechanism to the \CFA direct communication.) 2377 The program main in both programs communicates directly with the other thread versus indirect communication where two threads interact through a passive monitor. 2378 Both direct and indirection thread communication are valuable tools in structuring concurrent programs. 2366 \subsection{Details: Interaction with polymorphism} 2367 Depending on the choice of semantics for when monitor locks are acquired, interaction between monitors and \CFA's concept of polymorphism can be more complex to support. 2368 However, it is shown that entry-point locking solves most of the issues. 2369 2370 First of all, interaction between @otype@ polymorphism (see Section~\ref{s:ParametricPolymorphism}) and monitors is impossible since monitors do not support copying. 2371 Therefore, the main question is how to support @dtype@ polymorphism. 2372 It is important to present the difference between the two acquiring options: \textbf{callsite-locking} and entry-point locking, \ie acquiring the monitors before making a mutex routine-call or as the first operation of the mutex routine-call. 2373 For example: 2374 \begin{table} 2375 \begin{center} 2376 \begin{tabular}{|c|c|c|} 2377 Mutex & \textbf{callsite-locking} & \textbf{entry-point-locking} \\ 2378 call & cfa-code & cfa-code \\ 2379 \hline 2380 \begin{cfa}[tabsize=3] 2381 void foo(monitor& mutex a){ 2382 2383 // Do Work 2384 //... 2385 2386 } 2387 2388 void main() { 2389 monitor a; 2390 2391 foo(a); 2392 2393 } 2394 \end{cfa} & \begin{cfa}[tabsize=3] 2395 foo(& a) { 2396 2397 // Do Work 2398 //... 2399 2400 } 2401 2402 main() { 2403 monitor a; 2404 acquire(a); 2405 foo(a); 2406 release(a); 2407 } 2408 \end{cfa} & \begin{cfa}[tabsize=3] 2409 foo(& a) { 2410 acquire(a); 2411 // Do Work 2412 //... 2413 release(a); 2414 } 2415 2416 main() { 2417 monitor a; 2418 2419 foo(a); 2420 2421 } 2422 \end{cfa} 2423 \end{tabular} 2424 \end{center} 2425 \caption{Call-site vs entry-point locking for mutex calls} 2426 \label{tbl:locking-site} 2427 \end{table} 2428 2429 Note the @mutex@ keyword relies on the type system, which means that in cases where a generic monitor-routine is desired, writing the mutex routine is possible with the proper trait, \eg: 2430 \begin{cfa} 2431 // Incorrect: T may not be monitor 2432 forall(dtype T) 2433 void foo(T * mutex t); 2434 2435 // Correct: this routine only works on monitors (any monitor) 2436 forall(dtype T | is_monitor(T)) 2437 void bar(T * mutex t)); 2438 \end{cfa} 2439 2440 Both entry point and \textbf{callsite-locking} are feasible implementations. 2441 The current \CFA implementation uses entry-point locking because it requires less work when using \textbf{raii}, effectively transferring the burden of implementation to object construction/destruction. 2442 It is harder to use \textbf{raii} for call-site locking, as it does not necessarily have an existing scope that matches exactly the scope of the mutual exclusion, \ie the routine body. 2443 For example, the monitor call can appear in the middle of an expression. 2444 Furthermore, entry-point locking requires less code generation since any useful routine is called multiple times but there is only one entry point for many call sites. 2445 2446 % ====================================================================== 2447 % ====================================================================== 2448 \section{Threading} \label{impl:thread} 2449 % ====================================================================== 2450 % ====================================================================== 2451 2452 Figure \ref{fig:system1} shows a high-level picture if the \CFA runtime system in regards to concurrency. 2453 Each component of the picture is explained in detail in the flowing sections. 2379 2454 2380 2455 \begin{figure} 2381 \centering 2382 \begin{lrbox}{\myboxA} 2383 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2384 2385 struct Msg { int i, j; }; 2386 thread GoRtn { int i; float f; Msg m; }; 2387 void mem1( GoRtn & mutex gortn, int i ) { gortn.i = i; } 2388 void mem2( GoRtn & mutex gortn, float f ) { gortn.f = f; } 2389 void mem3( GoRtn & mutex gortn, Msg m ) { gortn.m = m; } 2390 void ^?{}( GoRtn & mutex ) {} 2391 2392 void main( GoRtn & gortn ) with( gortn ) { // thread starts 2393 2394 for () { 2395 2396 `waitfor( mem1, gortn )` sout | i; // wait for calls 2397 or `waitfor( mem2, gortn )` sout | f; 2398 or `waitfor( mem3, gortn )` sout | m.i | m.j; 2399 or `waitfor( ^?{}, gortn )` break; 2400 2456 \begin{center} 2457 {\resizebox{\textwidth}{!}{\input{system.pstex_t}}} 2458 \end{center} 2459 \caption{Overview of the entire system} 2460 \label{fig:system1} 2461 \end{figure} 2462 2463 \subsection{Processors} 2464 Parallelism in \CFA is built around using processors to specify how much parallelism is desired. \CFA processors are object wrappers around kernel threads, specifically @pthread@s in the current implementation of \CFA. 2465 Indeed, any parallelism must go through operating-system libraries. 2466 However, \textbf{uthread} are still the main source of concurrency, processors are simply the underlying source of parallelism. 2467 Indeed, processor \textbf{kthread} simply fetch a \textbf{uthread} from the scheduler and run it; they are effectively executers for user-threads. 2468 The main benefit of this approach is that it offers a well-defined boundary between kernel code and user code, for example, kernel thread quiescing, scheduling and interrupt handling. 2469 Processors internally use coroutines to take advantage of the existing context-switching semantics. 2470 2471 \subsection{Stack Management} 2472 One of the challenges of this system is to reduce the footprint as much as possible. 2473 Specifically, all @pthread@s created also have a stack created with them, which should be used as much as possible. 2474 Normally, coroutines also create their own stack to run on, however, in the case of the coroutines used for processors, these coroutines run directly on the \textbf{kthread} stack, effectively stealing the processor stack. 2475 The exception to this rule is the Main Processor, \ie the initial \textbf{kthread} that is given to any program. 2476 In order to respect C user expectations, the stack of the initial kernel thread, the main stack of the program, is used by the main user thread rather than the main processor, which can grow very large. 2477 2478 \subsection{Context Switching} 2479 As mentioned in section \ref{coroutine}, coroutines are a stepping stone for implementing threading, because they share the same mechanism for context-switching between different stacks. 2480 To improve performance and simplicity, context-switching is implemented using the following assumption: all context-switches happen inside a specific routine call. 2481 This assumption means that the context-switch only has to copy the callee-saved registers onto the stack and then switch the stack registers with the ones of the target coroutine/thread. 2482 Note that the instruction pointer can be left untouched since the context-switch is always inside the same routine 2483 Threads, however, do not context-switch between each other directly. 2484 They context-switch to the scheduler. 2485 This method is called a 2-step context-switch and has the advantage of having a clear distinction between user code and the kernel where scheduling and other system operations happen. 2486 Obviously, this doubles the context-switch cost because threads must context-switch to an intermediate stack. 2487 The alternative 1-step context-switch uses the stack of the ``from'' thread to schedule and then context-switches directly to the ``to'' thread. 2488 However, the performance of the 2-step context-switch is still superior to a @pthread_yield@ (see section \ref{results}). 2489 Additionally, for users in need for optimal performance, it is important to note that having a 2-step context-switch as the default does not prevent \CFA from offering a 1-step context-switch (akin to the Microsoft @SwitchToFiber@~\cite{switchToWindows} routine). 2490 This option is not currently present in \CFA, but the changes required to add it are strictly additive. 2491 2492 \subsection{Preemption} \label{preemption} 2493 Finally, an important aspect for any complete threading system is preemption. 2494 As mentioned in section \ref{basics}, preemption introduces an extra degree of uncertainty, which enables users to have multiple threads interleave transparently, rather than having to cooperate among threads for proper scheduling and CPU distribution. 2495 Indeed, preemption is desirable because it adds a degree of isolation among threads. 2496 In a fully cooperative system, any thread that runs a long loop can starve other threads, while in a preemptive system, starvation can still occur but it does not rely on every thread having to yield or block on a regular basis, which reduces significantly a programmer burden. 2497 Obviously, preemption is not optimal for every workload. 2498 However any preemptive system can become a cooperative system by making the time slices extremely large. 2499 Therefore, \CFA uses a preemptive threading system. 2500 2501 Preemption in \CFA\footnote{Note that the implementation of preemption is strongly tied with the underlying threading system. 2502 For this reason, only the Linux implementation is cover, \CFA does not run on Windows at the time of writting} is based on kernel timers, which are used to run a discrete-event simulation. 2503 Every processor keeps track of the current time and registers an expiration time with the preemption system. 2504 When the preemption system receives a change in preemption, it inserts the time in a sorted order and sets a kernel timer for the closest one, effectively stepping through preemption events on each signal sent by the timer. 2505 These timers use the Linux signal {\tt SIGALRM}, which is delivered to the process rather than the kernel-thread. 2506 This results in an implementation problem, because when delivering signals to a process, the kernel can deliver the signal to any kernel thread for which the signal is not blocked, \ie: 2507 \begin{quote} 2508 A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. 2509 If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which to deliver the signal. 2510 SIGNAL(7) - Linux Programmer's Manual 2511 \end{quote} 2512 For the sake of simplicity, and in order to prevent the case of having two threads receiving alarms simultaneously, \CFA programs block the {\tt SIGALRM} signal on every kernel thread except one. 2513 2514 Now because of how involuntary context-switches are handled, the kernel thread handling {\tt SIGALRM} cannot also be a processor thread. 2515 Hence, involuntary context-switching is done by sending signal {\tt SIGUSR1} to the corresponding proces\-sor and having the thread yield from inside the signal handler. 2516 This approach effectively context-switches away from the signal handler back to the kernel and the signal handler frame is eventually unwound when the thread is scheduled again. 2517 As a result, a signal handler can start on one kernel thread and terminate on a second kernel thread (but the same user thread). 2518 It is important to note that signal handlers save and restore signal masks because user-thread migration can cause a signal mask to migrate from one kernel thread to another. 2519 This behaviour is only a problem if all kernel threads, among which a user thread can migrate, differ in terms of signal masks\footnote{Sadly, official POSIX documentation is silent on what distinguishes ``async-signal-safe'' routines from other routines}. 2520 However, since the kernel thread handling preemption requires a different signal mask, executing user threads on the kernel-alarm thread can cause deadlocks. 2521 For this reason, the alarm thread is in a tight loop around a system call to @sigwaitinfo@, requiring very little CPU time for preemption. 2522 One final detail about the alarm thread is how to wake it when additional communication is required (\eg on thread termination). 2523 This unblocking is also done using {\tt SIGALRM}, but sent through the @pthread_sigqueue@. 2524 Indeed, @sigwait@ can differentiate signals sent from @pthread_sigqueue@ from signals sent from alarms or the kernel. 2525 2526 \subsection{Scheduler} 2527 Finally, an aspect that was not mentioned yet is the scheduling algorithm. 2528 Currently, the \CFA scheduler uses a single ready queue for all processors, which is the simplest approach to scheduling. 2529 Further discussion on scheduling is present in section \ref{futur:sched}. 2530 2531 % ====================================================================== 2532 % ====================================================================== 2533 \section{Internal Scheduling} \label{impl:intsched} 2534 % ====================================================================== 2535 % ====================================================================== 2536 The following figure is the traditional illustration of a monitor (repeated from page~\pageref{fig:ClassicalMonitor} for convenience): 2537 2538 \begin{figure} 2539 \begin{center} 2540 {\resizebox{0.4\textwidth}{!}{\input{monitor}}} 2541 \end{center} 2542 \caption{Traditional illustration of a monitor} 2543 \end{figure} 2544 2545 This picture has several components, the two most important being the entry queue and the AS-stack. 2546 The entry queue is an (almost) FIFO list where threads waiting to enter are parked, while the acceptor/signaller (AS) stack is a FILO list used for threads that have been signalled or otherwise marked as running next. 2547 2548 For \CFA, this picture does not have support for blocking multiple monitors on a single condition. 2549 To support bulk acquire two changes to this picture are required. 2550 First, it is no longer helpful to attach the condition to \emph{a single} monitor. 2551 Secondly, the thread waiting on the condition has to be separated across multiple monitors, seen in figure \ref{fig:monitor_cfa}. 2552 2553 \begin{figure} 2554 \begin{center} 2555 {\resizebox{0.8\textwidth}{!}{\input{int_monitor}}} 2556 \end{center} 2557 \caption{Illustration of \CFA Monitor} 2558 \label{fig:monitor_cfa} 2559 \end{figure} 2560 2561 This picture and the proper entry and leave algorithms (see listing \ref{f:entry2}) is the fundamental implementation of internal scheduling. 2562 Note that when a thread is moved from the condition to the AS-stack, it is conceptually split into N pieces, where N is the number of monitors specified in the parameter list. 2563 The thread is woken up when all the pieces have popped from the AS-stacks and made active. 2564 In this picture, the threads are split into halves but this is only because there are two monitors. 2565 For a specific signalling operation every monitor needs a piece of thread on its AS-stack. 2566 2567 \begin{figure} 2568 \begin{multicols}{2} 2569 Entry 2570 \begin{cfa} 2571 if monitor is free 2572 enter 2573 elif already own the monitor 2574 continue 2575 else 2576 block 2577 increment recursion 2578 2579 \end{cfa} 2580 \columnbreak 2581 Exit 2582 \begin{cfa} 2583 decrement recursion 2584 if recursion == 0 2585 if signal_stack not empty 2586 set_owner to thread 2587 if all monitors ready 2588 wake-up thread 2589 2590 if entry queue not empty 2591 wake-up thread 2592 \end{cfa} 2593 \end{multicols} 2594 \begin{cfa}[caption={Entry and exit routine for monitors with internal scheduling},label={f:entry2}] 2595 \end{cfa} 2596 \end{figure} 2597 2598 The solution discussed in \ref{s:InternalScheduling} can be seen in the exit routine of listing \ref{f:entry2}. 2599 Basically, the solution boils down to having a separate data structure for the condition queue and the AS-stack, and unconditionally transferring ownership of the monitors but only unblocking the thread when the last monitor has transferred ownership. 2600 This solution is deadlock safe as well as preventing any potential barging. 2601 The data structures used for the AS-stack are reused extensively for external scheduling, but in the case of internal scheduling, the data is allocated using variable-length arrays on the call stack of the @wait@ and @signal_block@ routines. 2602 2603 \begin{figure} 2604 \begin{center} 2605 {\resizebox{0.8\textwidth}{!}{\input{monitor_structs.pstex_t}}} 2606 \end{center} 2607 \caption{Data structures involved in internal/external scheduling} 2608 \label{fig:structs} 2609 \end{figure} 2610 2611 Figure \ref{fig:structs} shows a high-level representation of these data structures. 2612 The main idea behind them is that, a thread cannot contain an arbitrary number of intrusive ``next'' pointers for linking onto monitors. 2613 The @condition node@ is the data structure that is queued onto a condition variable and, when signalled, the condition queue is popped and each @condition criterion@ is moved to the AS-stack. 2614 Once all the criteria have been popped from their respective AS-stacks, the thread is woken up, which is what is shown in listing \ref{f:entry2}. 2615 2616 % ====================================================================== 2617 % ====================================================================== 2618 \section{External Scheduling} 2619 % ====================================================================== 2620 % ====================================================================== 2621 Similarly to internal scheduling, external scheduling for multiple monitors relies on the idea that waiting-thread queues are no longer specific to a single monitor, as mentioned in section \ref{extsched}. 2622 For internal scheduling, these queues are part of condition variables, which are still unique for a given scheduling operation (\ie no signal statement uses multiple conditions). 2623 However, in the case of external scheduling, there is no equivalent object which is associated with @waitfor@ statements. 2624 This absence means the queues holding the waiting threads must be stored inside at least one of the monitors that is acquired. 2625 These monitors being the only objects that have sufficient lifetime and are available on both sides of the @waitfor@ statement. 2626 This requires an algorithm to choose which monitor holds the relevant queue. 2627 It is also important that said algorithm be independent of the order in which users list parameters. 2628 The proposed algorithm is to fall back on monitor lock ordering (sorting by address) and specify that the monitor that is acquired first is the one with the relevant waiting queue. 2629 This assumes that the lock acquiring order is static for the lifetime of all concerned objects but that is a reasonable constraint. 2630 2631 This algorithm choice has two consequences: 2632 \begin{itemize} 2633 \item The queue of the monitor with the lowest address is no longer a true FIFO queue because threads can be moved to the front of the queue. 2634 These queues need to contain a set of monitors for each of the waiting threads. 2635 Therefore, another thread whose set contains the same lowest address monitor but different lower priority monitors may arrive first but enter the critical section after a thread with the correct pairing. 2636 \item The queue of the lowest priority monitor is both required and potentially unused. 2637 Indeed, since it is not known at compile time which monitor is the monitor which has the lowest address, every monitor needs to have the correct queues even though it is possible that some queues go unused for the entire duration of the program, for example if a monitor is only used in a specific pair. 2638 \end{itemize} 2639 Therefore, the following modifications need to be made to support external scheduling: 2640 \begin{itemize} 2641 \item The threads waiting on the entry queue need to keep track of which routine they are trying to enter, and using which set of monitors. 2642 The @mutex@ routine already has all the required information on its stack, so the thread only needs to keep a pointer to that information. 2643 \item The monitors need to keep a mask of acceptable routines. 2644 This mask contains for each acceptable routine, a routine pointer and an array of monitors to go with it. 2645 It also needs storage to keep track of which routine was accepted. 2646 Since this information is not specific to any monitor, the monitors actually contain a pointer to an integer on the stack of the waiting thread. 2647 Note that if a thread has acquired two monitors but executes a @waitfor@ with only one monitor as a parameter, setting the mask of acceptable routines to both monitors will not cause any problems since the extra monitor will not change ownership regardless. 2648 This becomes relevant when @when@ clauses affect the number of monitors passed to a @waitfor@ statement. 2649 \item The entry/exit routines need to be updated as shown in listing \ref{f:entry3}. 2650 \end{itemize} 2651 2652 \subsection{External Scheduling - Destructors} 2653 Finally, to support the ordering inversion of destructors, the code generation needs to be modified to use a special entry routine. 2654 This routine is needed because of the storage requirements of the call order inversion. 2655 Indeed, when waiting for the destructors, storage is needed for the waiting context and the lifetime of said storage needs to outlive the waiting operation it is needed for. 2656 For regular @waitfor@ statements, the call stack of the routine itself matches this requirement but it is no longer the case when waiting for the destructor since it is pushed on to the AS-stack for later. 2657 The @waitfor@ semantics can then be adjusted correspondingly, as seen in listing \ref{f:entry-dtor} 2658 2659 \begin{figure} 2660 \begin{multicols}{2} 2661 Entry 2662 \begin{cfa} 2663 if monitor is free 2664 enter 2665 elif already own the monitor 2666 continue 2667 elif matches waitfor mask 2668 push criteria to AS-stack 2669 continue 2670 else 2671 block 2672 increment recursion 2673 \end{cfa} 2674 \columnbreak 2675 Exit 2676 \begin{cfa} 2677 decrement recursion 2678 if recursion == 0 2679 if signal_stack not empty 2680 set_owner to thread 2681 if all monitors ready 2682 wake-up thread 2683 endif 2684 endif 2685 2686 if entry queue not empty 2687 wake-up thread 2688 endif 2689 \end{cfa} 2690 \end{multicols} 2691 \begin{cfa}[caption={Entry and exit routine for monitors with internal scheduling and external scheduling},label={f:entry3}] 2692 \end{cfa} 2693 \end{figure} 2694 2695 \begin{figure} 2696 \begin{multicols}{2} 2697 Destructor Entry 2698 \begin{cfa} 2699 if monitor is free 2700 enter 2701 elif already own the monitor 2702 increment recursion 2703 return 2704 create wait context 2705 if matches waitfor mask 2706 reset mask 2707 push self to AS-stack 2708 baton pass 2709 else 2710 wait 2711 increment recursion 2712 \end{cfa} 2713 \columnbreak 2714 Waitfor 2715 \begin{cfa} 2716 if matching thread is already there 2717 if found destructor 2718 push destructor to AS-stack 2719 unlock all monitors 2720 else 2721 push self to AS-stack 2722 baton pass 2723 endif 2724 return 2725 endif 2726 if non-blocking 2727 Unlock all monitors 2728 Return 2729 endif 2730 2731 push self to AS-stack 2732 set waitfor mask 2733 block 2734 return 2735 \end{cfa} 2736 \end{multicols} 2737 \begin{cfa}[caption={Pseudo code for the \protect\lstinline|waitfor| routine and the \protect\lstinline|mutex| entry routine for destructors},label={f:entry-dtor}] 2738 \end{cfa} 2739 \end{figure} 2740 2741 2742 % ====================================================================== 2743 % ====================================================================== 2744 \section{Putting It All Together} 2745 % ====================================================================== 2746 % ====================================================================== 2747 2748 2749 \section{Threads As Monitors} 2750 As it was subtly alluded in section \ref{threads}, @thread@s in \CFA are in fact monitors, which means that all monitor features are available when using threads. 2751 For example, here is a very simple two thread pipeline that could be used for a simulator of a game engine: 2752 \begin{figure} 2753 \begin{cfa}[caption={Toy simulator using \protect\lstinline|thread|s and \protect\lstinline|monitor|s.},label={f:engine-v1}] 2754 // Visualization declaration 2755 thread Renderer {} renderer; 2756 Frame * simulate( Simulator & this ); 2757 2758 // Simulation declaration 2759 thread Simulator{} simulator; 2760 void render( Renderer & this ); 2761 2762 // Blocking call used as communication 2763 void draw( Renderer & mutex this, Frame * frame ); 2764 2765 // Simulation loop 2766 void main( Simulator & this ) { 2767 while( true ) { 2768 Frame * frame = simulate( this ); 2769 draw( renderer, frame ); 2401 2770 } 2402 2403 } 2404 int main() { 2405 GoRtn gortn; $\C[2.0in]{// start thread}$ 2406 `mem1( gortn, 0 );` $\C{// different calls}\CRT$ 2407 `mem2( gortn, 2.5 );` 2408 `mem3( gortn, (Msg){1, 2} );` 2409 2410 2411 } // wait for completion 2412 \end{cfa} 2413 \end{lrbox} 2414 2415 \begin{lrbox}{\myboxB} 2416 \begin{Go}[aboveskip=0pt,belowskip=0pt] 2417 func main() { 2418 type Msg struct{ i, j int } 2419 2420 ch1 := make( chan int ) 2421 ch2 := make( chan float32 ) 2422 ch3 := make( chan Msg ) 2423 hand := make( chan string ) 2424 shake := make( chan string ) 2425 gortn := func() { $\C[1.5in]{// thread starts}$ 2426 var i int; var f float32; var m Msg 2427 L: for { 2428 select { $\C{// wait for messages}$ 2429 case `i = <- ch1`: fmt.Println( i ) 2430 case `f = <- ch2`: fmt.Println( f ) 2431 case `m = <- ch3`: fmt.Println( m ) 2432 case `<- hand`: break L $\C{// sentinel}$ 2433 } 2771 } 2772 2773 // Rendering loop 2774 void main( Renderer & this ) { 2775 while( true ) { 2776 waitfor( draw, this ); 2777 render( this ); 2778 } 2779 } 2780 \end{cfa} 2781 \end{figure} 2782 One of the obvious complaints of the previous code snippet (other than its toy-like simplicity) is that it does not handle exit conditions and just goes on forever. 2783 Luckily, the monitor semantics can also be used to clearly enforce a shutdown order in a concise manner: 2784 \begin{figure} 2785 \begin{cfa}[caption={Same toy simulator with proper termination condition.},label={f:engine-v2}] 2786 // Visualization declaration 2787 thread Renderer {} renderer; 2788 Frame * simulate( Simulator & this ); 2789 2790 // Simulation declaration 2791 thread Simulator{} simulator; 2792 void render( Renderer & this ); 2793 2794 // Blocking call used as communication 2795 void draw( Renderer & mutex this, Frame * frame ); 2796 2797 // Simulation loop 2798 void main( Simulator & this ) { 2799 while( true ) { 2800 Frame * frame = simulate( this ); 2801 draw( renderer, frame ); 2802 2803 // Exit main loop after the last frame 2804 if( frame->is_last ) break; 2805 } 2806 } 2807 2808 // Rendering loop 2809 void main( Renderer & this ) { 2810 while( true ) { 2811 waitfor( draw, this ); 2812 or waitfor( ^?{}, this ) { 2813 // Add an exit condition 2814 break; 2434 2815 } 2435 `shake <- "SHAKE"` $\C{// completion}$ 2816 2817 render( this ); 2436 2818 } 2437 2438 go gortn() $\C{// start thread}$ 2439 `ch1 <- 0` $\C{// different messages}$ 2440 `ch2 <- 2.5` 2441 `ch3 <- Msg{1, 2}` 2442 `hand <- "HAND"` $\C{// sentinel value}$ 2443 `<- shake` $\C{// wait for completion}\CRT$ 2444 } 2445 \end{Go} 2446 \end{lrbox} 2447 2448 \subfloat[\CFA]{\label{f:CFAwaitfor}\usebox\myboxA} 2449 \hspace{3pt} 2450 \vrule 2451 \hspace{3pt} 2452 \subfloat[Go]{\label{f:Gochannel}\usebox\myboxB} 2453 \caption{Direct communication} 2454 \label{f:DirectCommunication} 2819 } 2820 2821 // Call destructor for simulator once simulator finishes 2822 // Call destructor for renderer to signify shutdown 2823 \end{cfa} 2455 2824 \end{figure} 2456 2825 2457 \begin{comment} 2458 The following shows an example of two threads directly calling each other and accepting calls from each other in a cycle. 2459 \begin{cfa} 2460 \end{cfa} 2461 \vspace{-0.8\baselineskip} 2462 \begin{cquote} 2463 \begin{tabular}{@{}l@{\hspace{3\parindentlnth}}l@{}} 2464 \begin{cfa} 2465 thread Ping {} pi; 2466 void ping( Ping & mutex ) {} 2467 void main( Ping & pi ) { 2468 for ( 10 ) { 2469 `waitfor( ping, pi );` 2470 `pong( po );` 2471 } 2472 } 2473 int main() {} 2474 \end{cfa} 2475 & 2476 \begin{cfa} 2477 thread Pong {} po; 2478 void pong( Pong & mutex ) {} 2479 void main( Pong & po ) { 2480 for ( 10 ) { 2481 `ping( pi );` 2482 `waitfor( pong, po );` 2483 } 2484 } 2485 2486 \end{cfa} 2487 \end{tabular} 2488 \end{cquote} 2489 % \lstMakeShortInline@% 2490 % \caption{Threads ping/pong using external scheduling} 2491 % \label{f:pingpong} 2492 % \end{figure} 2493 Note, the ping/pong threads are globally declared, @pi@/@po@, and hence, start (and possibly complete) before the program main starts. 2494 \end{comment} 2495 2496 2497 \subsection{Execution Properties} 2498 2499 Table~\ref{t:ObjectPropertyComposition} shows how the \CFA high-level constructs cover 3 fundamental execution properties: thread, stateful function, and mutual exclusion. 2500 Case 1 is a basic object, with none of the new execution properties. 2501 Case 2 allows @mutex@ calls to Case 1 to protect shared data. 2502 Case 3 allows stateful functions to suspend/resume but restricts operations because the state is stackless. 2503 Case 4 allows @mutex@ calls to Case 3 to protect shared data. 2504 Cases 5 and 6 are the same as 3 and 4 without restriction because the state is stackful. 2505 Cases 7 and 8 are rejected because a thread cannot execute without a stackful state in a preemptive environment when context switching from the signal handler. 2506 Cases 9 and 10 have a stackful thread without and with @mutex@ calls. 2507 For situations where threads do not require direct communication, case 9 provides faster creation/destruction by eliminating @mutex@ setup. 2508 2826 \section{Fibers \& Threads} 2827 As mentioned in section \ref{preemption}, \CFA uses preemptive threads by default but can use fibers on demand. 2828 Currently, using fibers is done by adding the following line of code to the program~: 2829 \begin{cfa} 2830 unsigned int default_preemption() { 2831 return 0; 2832 } 2833 \end{cfa} 2834 This routine is called by the kernel to fetch the default preemption rate, where 0 signifies an infinite time-slice, \ie no preemption. 2835 However, once clusters are fully implemented, it will be possible to create fibers and \textbf{uthread} in the same system, as in listing \ref{f:fiber-uthread} 2836 \begin{figure} 2837 \lstset{language=CFA,deletedelim=**[is][]{`}{`}} 2838 \begin{cfa}[caption={Using fibers and \textbf{uthread} side-by-side in \CFA},label={f:fiber-uthread}] 2839 // Cluster forward declaration 2840 struct cluster; 2841 2842 // Processor forward declaration 2843 struct processor; 2844 2845 // Construct clusters with a preemption rate 2846 void ?{}(cluster& this, unsigned int rate); 2847 // Construct processor and add it to cluster 2848 void ?{}(processor& this, cluster& cluster); 2849 // Construct thread and schedule it on cluster 2850 void ?{}(thread& this, cluster& cluster); 2851 2852 // Declare two clusters 2853 cluster thread_cluster = { 10`ms }; // Preempt every 10 ms 2854 cluster fibers_cluster = { 0 }; // Never preempt 2855 2856 // Construct 4 processors 2857 processor processors[4] = { 2858 //2 for the thread cluster 2859 thread_cluster; 2860 thread_cluster; 2861 //2 for the fibers cluster 2862 fibers_cluster; 2863 fibers_cluster; 2864 }; 2865 2866 // Declares thread 2867 thread UThread {}; 2868 void ?{}(UThread& this) { 2869 // Construct underlying thread to automatically 2870 // be scheduled on the thread cluster 2871 (this){ thread_cluster } 2872 } 2873 2874 void main(UThread & this); 2875 2876 // Declares fibers 2877 thread Fiber {}; 2878 void ?{}(Fiber& this) { 2879 // Construct underlying thread to automatically 2880 // be scheduled on the fiber cluster 2881 (this.__thread){ fibers_cluster } 2882 } 2883 2884 void main(Fiber & this); 2885 \end{cfa} 2886 \end{figure} 2887 2888 2889 % ====================================================================== 2890 % ====================================================================== 2891 \section{Performance Results} \label{results} 2892 % ====================================================================== 2893 % ====================================================================== 2894 \section{Machine Setup} 2895 Table \ref{tab:machine} shows the characteristics of the machine used to run the benchmarks. 2896 All tests were made on this machine. 2509 2897 \begin{table} 2510 \caption{Object property composition} 2511 \centering 2512 \label{t:ObjectPropertyComposition} 2513 \renewcommand{\arraystretch}{1.25} 2514 %\setlength{\tabcolsep}{5pt} 2515 \begin{tabular}{c|c||l|l} 2516 \multicolumn{2}{c||}{object properties} & \multicolumn{2}{c}{mutual exclusion} \\ 2898 \begin{center} 2899 \begin{tabular}{| l | r | l | r |} 2517 2900 \hline 2518 thread & stateful & \multicolumn{1}{c|}{No} & \multicolumn{1}{c}{Yes} \\ 2901 Architecture & x86\_64 & NUMA node(s) & 8 \\ 2902 \hline 2903 CPU op-mode(s) & 32-bit, 64-bit & Model name & AMD Opteron\texttrademark Processor 6380 \\ 2904 \hline 2905 Byte Order & Little Endian & CPU Freq & 2.5\si{\giga\hertz} \\ 2906 \hline 2907 CPU(s) & 64 & L1d cache & \SI{16}{\kibi\byte} \\ 2908 \hline 2909 Thread(s) per core & 2 & L1i cache & \SI{64}{\kibi\byte} \\ 2910 \hline 2911 Core(s) per socket & 8 & L2 cache & \SI{2048}{\kibi\byte} \\ 2912 \hline 2913 Socket(s) & 4 & L3 cache & \SI{6144}{\kibi\byte} \\ 2519 2914 \hline 2520 2915 \hline 2521 No & No & \textbf{1}\ \ \ aggregate type & \textbf{2}\ \ \ @monitor@ aggregate type\\2916 Operating system & Ubuntu 16.04.3 LTS & Kernel & Linux 4.4-97-generic \\ 2522 2917 \hline 2523 No & Yes (stackless) & \textbf{3}\ \ \ @generator@ & \textbf{4}\ \ \ @monitor@ @generator@\\2918 Compiler & GCC 6.3 & Translator & CFA 1 \\ 2524 2919 \hline 2525 No & Yes (stackful) & \textbf{5}\ \ \ @coroutine@ & \textbf{6}\ \ \ @monitor@ @coroutine@\\2920 Java version & OpenJDK-9 & Go version & 1.9.2 \\ 2526 2921 \hline 2527 Yes & No / Yes (stackless) & \textbf{7}\ \ \ {\color{red}rejected} & \textbf{8}\ \ \ {\color{red}rejected} \\ 2922 \end{tabular} 2923 \end{center} 2924 \caption{Machine setup used for the tests} 2925 \label{tab:machine} 2926 \end{table} 2927 2928 \section{Micro Benchmarks} 2929 All benchmarks are run using the same harness to produce the results, seen as the @BENCH()@ macro in the following examples. 2930 This macro uses the following logic to benchmark the code: 2931 \begin{cfa} 2932 #define BENCH(run, result) \ 2933 before = gettime(); \ 2934 run; \ 2935 after = gettime(); \ 2936 result = (after - before) / N; 2937 \end{cfa} 2938 The method used to get time is @clock_gettime(CLOCK_THREAD_CPUTIME_ID);@. 2939 Each benchmark is using many iterations of a simple call to measure the cost of the call. 2940 The specific number of iterations depends on the specific benchmark. 2941 2942 \subsection{Context-Switching} 2943 The first interesting benchmark is to measure how long context-switches take. 2944 The simplest approach to do this is to yield on a thread, which executes a 2-step context switch. 2945 Yielding causes the thread to context-switch to the scheduler and back, more precisely: from the \textbf{uthread} to the \textbf{kthread} then from the \textbf{kthread} back to the same \textbf{uthread} (or a different one in the general case). 2946 In order to make the comparison fair, coroutines also execute a 2-step context-switch by resuming another coroutine which does nothing but suspending in a tight loop, which is a resume/suspend cycle instead of a yield. 2947 Figure~\ref{f:ctx-switch} shows the code for coroutines and threads with the results in table \ref{tab:ctx-switch}. 2948 All omitted tests are functionally identical to one of these tests. 2949 The difference between coroutines and threads can be attributed to the cost of scheduling. 2950 \begin{figure} 2951 \begin{multicols}{2} 2952 \CFA Coroutines 2953 \begin{cfa} 2954 coroutine GreatSuspender {}; 2955 void main(GreatSuspender& this) { 2956 while(true) { suspend(); } 2957 } 2958 int main() { 2959 GreatSuspender s; 2960 resume(s); 2961 BENCH( 2962 for(size_t i=0; i<n; i++) { 2963 resume(s); 2964 }, 2965 result 2966 ) 2967 printf("%llu\n", result); 2968 } 2969 \end{cfa} 2970 \columnbreak 2971 \CFA Threads 2972 \begin{cfa} 2973 2974 2975 2976 2977 int main() { 2978 2979 2980 BENCH( 2981 for(size_t i=0; i<n; i++) { 2982 yield(); 2983 }, 2984 result 2985 ) 2986 printf("%llu\n", result); 2987 } 2988 \end{cfa} 2989 \end{multicols} 2990 \begin{cfa}[caption={\CFA benchmark code used to measure context-switches for coroutines and threads.},label={f:ctx-switch}] 2991 \end{cfa} 2992 \end{figure} 2993 2994 \begin{table} 2995 \begin{center} 2996 \begin{tabular}{| l | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] |} 2997 \cline{2-4} 2998 \multicolumn{1}{c |}{} & \multicolumn{1}{c |}{ Median } &\multicolumn{1}{c |}{ Average } & \multicolumn{1}{c |}{ Standard Deviation} \\ 2528 2999 \hline 2529 Yes & Yes (stackful) & \textbf{9}\ \ \ @thread@ & \textbf{10}\ \ @monitor@ @thread@ \\ 3000 Kernel Thread & 241.5 & 243.86 & 5.08 \\ 3001 \CFA Coroutine & 38 & 38 & 0 \\ 3002 \CFA Thread & 103 & 102.96 & 2.96 \\ 3003 \uC Coroutine & 46 & 45.86 & 0.35 \\ 3004 \uC Thread & 98 & 99.11 & 1.42 \\ 3005 Goroutine & 150 & 149.96 & 3.16 \\ 3006 Java Thread & 289 & 290.68 & 8.72 \\ 3007 \hline 2530 3008 \end{tabular} 3009 \end{center} 3010 \caption{Context Switch comparison. 3011 All numbers are in nanoseconds(\si{\nano\second})} 3012 \label{tab:ctx-switch} 2531 3013 \end{table} 2532 3014 2533 2534 \subsection{Low-level Locks} 2535 2536 For completeness and efficiency, \CFA provides a standard set of low-level locks: recursive mutex, condition, semaphore, barrier, \etc, and atomic instructions: @fetchAssign@, @fetchAdd@, @testSet@, @compareSet@, \etc. 2537 Some of these low-level mechanism are used in the \CFA runtime, but we strongly advocate using high-level mechanisms whenever possible. 2538 2539 2540 % \section{Parallelism} 2541 % \label{s:Parallelism} 2542 % 2543 % Historically, computer performance was about processor speeds. 2544 % However, with heat dissipation being a direct consequence of speed increase, parallelism is the new source for increased performance~\cite{Sutter05, Sutter05b}. 2545 % Therefore, high-performance applications must care about parallelism, which requires concurrency. 2546 % The lowest-level approach of parallelism is to use \newterm{kernel threads} in combination with semantics like @fork@, @join@, \etc. 2547 % However, kernel threads are better as an implementation tool because of complexity and higher cost. 2548 % Therefore, different abstractions are often layered onto kernel threads to simplify them, \eg pthreads. 2549 % 2550 % 2551 % \subsection{User Threads} 2552 % 2553 % A direct improvement on kernel threads is user threads, \eg Erlang~\cite{Erlang} and \uC~\cite{uC++book}. 2554 % This approach provides an interface that matches the language paradigms, gives more control over concurrency by the language runtime, and an abstract (and portable) interface to the underlying kernel threads across operating systems. 2555 % In many cases, user threads can be used on a much larger scale (100,000 threads). 2556 % Like kernel threads, user threads support preemption, which maximizes nondeterminism, but increases the potential for concurrency errors: race, livelock, starvation, and deadlock. 2557 % \CFA adopts user-threads to provide more flexibility and a low-cost mechanism to build any other concurrency approach, \eg thread pools and actors~\cite{Actors}. 2558 % 2559 % A variant of user thread is \newterm{fibres}, which removes preemption, \eg Go~\cite{Go} @goroutine@s. 2560 % Like functional programming, which removes mutation and its associated problems, removing preemption from concurrency reduces nondeterminism, making race and deadlock errors more difficult to generate. 2561 % However, preemption is necessary for fairness and to reduce tail-latency. 2562 % For concurrency that relies on spinning, if all cores spin the system is livelocked, whereas preemption breaks the livelock. 2563 2564 2565 \begin{comment} 2566 \subsection{Thread Pools} 2567 2568 In contrast to direct threading is indirect \newterm{thread pools}, \eg Java @executor@, where small jobs (work units) are inserted into a work pool for execution. 2569 If the jobs are dependent, \ie interact, there is an implicit/explicit dependency graph that ties them together. 2570 While removing direct concurrency, and hence the amount of context switching, thread pools significantly limit the interaction that can occur among jobs. 2571 Indeed, jobs should not block because that also blocks the underlying thread, which effectively means the CPU utilization, and therefore throughput, suffers. 2572 While it is possible to tune the thread pool with sufficient threads, it becomes difficult to obtain high throughput and good core utilization as job interaction increases. 2573 As well, concurrency errors return, which threads pools are suppose to mitigate. 3015 \subsection{Mutual-Exclusion} 3016 The next interesting benchmark is to measure the overhead to enter/leave a critical-section. 3017 For monitors, the simplest approach is to measure how long it takes to enter and leave a monitor routine. 3018 Figure~\ref{f:mutex} shows the code for \CFA. 3019 To put the results in context, the cost of entering a non-inline routine and the cost of acquiring and releasing a @pthread_mutex@ lock is also measured. 3020 The results can be shown in table \ref{tab:mutex}. 2574 3021 2575 3022 \begin{figure} 2576 \centering 2577 \begin{tabular}{@{}l|l@{}} 2578 \begin{cfa} 2579 struct Adder { 2580 int * row, cols; 2581 }; 2582 int operator()() { 2583 subtotal = 0; 2584 for ( int c = 0; c < cols; c += 1 ) 2585 subtotal += row[c]; 2586 return subtotal; 2587 } 2588 void ?{}( Adder * adder, int row[$\,$], int cols, int & subtotal ) { 2589 adder.[rows, cols, subtotal] = [rows, cols, subtotal]; 2590 } 2591 2592 2593 2594 2595 \end{cfa} 2596 & 2597 \begin{cfa} 3023 \begin{cfa}[caption={\CFA benchmark code used to measure mutex routines.},label={f:mutex}] 3024 monitor M {}; 3025 void __attribute__((noinline)) call( M & mutex m /*, m2, m3, m4*/ ) {} 3026 2598 3027 int main() { 2599 const int rows = 10, cols = 10; 2600 int matrix[rows][cols], subtotals[rows], total = 0; 2601 // read matrix 2602 Executor executor( 4 ); // kernel threads 2603 Adder * adders[rows]; 2604 for ( r; rows ) { // send off work for executor 2605 adders[r] = new( matrix[r], cols, &subtotal[r] ); 2606 executor.send( *adders[r] ); 2607 } 2608 for ( r; rows ) { // wait for results 2609 delete( adders[r] ); 2610 total += subtotals[r]; 2611 } 2612 sout | total; 2613 } 2614 \end{cfa} 3028 M m/*, m2, m3, m4*/; 3029 BENCH( 3030 for(size_t i=0; i<n; i++) { 3031 call(m/*, m2, m3, m4*/); 3032 }, 3033 result 3034 ) 3035 printf("%llu\n", result); 3036 } 3037 \end{cfa} 3038 \end{figure} 3039 3040 \begin{table} 3041 \begin{center} 3042 \begin{tabular}{| l | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] |} 3043 \cline{2-4} 3044 \multicolumn{1}{c |}{} & \multicolumn{1}{c |}{ Median } &\multicolumn{1}{c |}{ Average } & \multicolumn{1}{c |}{ Standard Deviation} \\ 3045 \hline 3046 C routine & 2 & 2 & 0 \\ 3047 FetchAdd + FetchSub & 26 & 26 & 0 \\ 3048 Pthreads Mutex Lock & 31 & 31.86 & 0.99 \\ 3049 \uC @monitor@ member routine & 30 & 30 & 0 \\ 3050 \CFA @mutex@ routine, 1 argument & 41 & 41.57 & 0.9 \\ 3051 \CFA @mutex@ routine, 2 argument & 76 & 76.96 & 1.57 \\ 3052 \CFA @mutex@ routine, 4 argument & 145 & 146.68 & 3.85 \\ 3053 Java synchronized routine & 27 & 28.57 & 2.6 \\ 3054 \hline 2615 3055 \end{tabular} 2616 \caption{Executor} 3056 \end{center} 3057 \caption{Mutex routine comparison. 3058 All numbers are in nanoseconds(\si{\nano\second})} 3059 \label{tab:mutex} 3060 \end{table} 3061 3062 \subsection{Internal Scheduling} 3063 The internal-scheduling benchmark measures the cost of waiting on and signalling a condition variable. 3064 Figure~\ref{f:int-sched} shows the code for \CFA, with results table \ref{tab:int-sched}. 3065 As with all other benchmarks, all omitted tests are functionally identical to one of these tests. 3066 3067 \begin{figure} 3068 \begin{cfa}[caption={Benchmark code for internal scheduling},label={f:int-sched}] 3069 volatile int go = 0; 3070 condition c; 3071 monitor M {}; 3072 M m1; 3073 3074 void __attribute__((noinline)) do_call( M & mutex a1 ) { signal(c); } 3075 3076 thread T {}; 3077 void ^?{}( T & mutex this ) {} 3078 void main( T & this ) { 3079 while(go == 0) { yield(); } 3080 while(go == 1) { do_call(m1); } 3081 } 3082 int __attribute__((noinline)) do_wait( M & mutex a1 ) { 3083 go = 1; 3084 BENCH( 3085 for(size_t i=0; i<n; i++) { 3086 wait(c); 3087 }, 3088 result 3089 ) 3090 printf("%llu\n", result); 3091 go = 0; 3092 return 0; 3093 } 3094 int main() { 3095 T t; 3096 return do_wait(m1); 3097 } 3098 \end{cfa} 2617 3099 \end{figure} 2618 \end{comment} 2619 2620 2621 \section{Runtime Structure} 2622 \label{s:CFARuntimeStructure} 2623 2624 Figure~\ref{f:RunTimeStructure} illustrates the runtime structure of a \CFA program. 2625 In addition to the new kinds of objects introduced by \CFA, there are two more runtime entities used to control parallel execution: cluster and (virtual) processor. 2626 An executing thread is illustrated by its containment in a processor. 3100 3101 \begin{table} 3102 \begin{center} 3103 \begin{tabular}{| l | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] |} 3104 \cline{2-4} 3105 \multicolumn{1}{c |}{} & \multicolumn{1}{c |}{ Median } &\multicolumn{1}{c |}{ Average } & \multicolumn{1}{c |}{ Standard Deviation} \\ 3106 \hline 3107 Pthreads Condition Variable & 5902.5 & 6093.29 & 714.78 \\ 3108 \uC @signal@ & 322 & 323 & 3.36 \\ 3109 \CFA @signal@, 1 @monitor@ & 352.5 & 353.11 & 3.66 \\ 3110 \CFA @signal@, 2 @monitor@ & 430 & 430.29 & 8.97 \\ 3111 \CFA @signal@, 4 @monitor@ & 594.5 & 606.57 & 18.33 \\ 3112 Java @notify@ & 13831.5 & 15698.21 & 4782.3 \\ 3113 \hline 3114 \end{tabular} 3115 \end{center} 3116 \caption{Internal scheduling comparison. 3117 All numbers are in nanoseconds(\si{\nano\second})} 3118 \label{tab:int-sched} 3119 \end{table} 3120 3121 \subsection{External Scheduling} 3122 The Internal scheduling benchmark measures the cost of the @waitfor@ statement (@_Accept@ in \uC). 3123 Figure~\ref{f:ext-sched} shows the code for \CFA, with results in table \ref{tab:ext-sched}. 3124 As with all other benchmarks, all omitted tests are functionally identical to one of these tests. 2627 3125 2628 3126 \begin{figure} 2629 \centering 2630 \input{RunTimeStructure} 2631 \caption{\CFA Runtime structure} 2632 \label{f:RunTimeStructure} 3127 \begin{cfa}[caption={Benchmark code for external scheduling},label={f:ext-sched}] 3128 volatile int go = 0; 3129 monitor M {}; 3130 M m1; 3131 thread T {}; 3132 3133 void __attribute__((noinline)) do_call( M & mutex a1 ) {} 3134 3135 void ^?{}( T & mutex this ) {} 3136 void main( T & this ) { 3137 while(go == 0) { yield(); } 3138 while(go == 1) { do_call(m1); } 3139 } 3140 int __attribute__((noinline)) do_wait( M & mutex a1 ) { 3141 go = 1; 3142 BENCH( 3143 for(size_t i=0; i<n; i++) { 3144 waitfor(call, a1); 3145 }, 3146 result 3147 ) 3148 printf("%llu\n", result); 3149 go = 0; 3150 return 0; 3151 } 3152 int main() { 3153 T t; 3154 return do_wait(m1); 3155 } 3156 \end{cfa} 2633 3157 \end{figure} 2634 3158 2635 2636 \subsection{Cluster} 2637 \label{s:RuntimeStructureCluster} 2638 2639 A \newterm{cluster} is a collection of threads and virtual processors (abstract kernel-thread) that execute the (user) threads from its own ready queue (like an OS executing kernel threads). 2640 The purpose of a cluster is to control the amount of parallelism that is possible among threads, plus scheduling and other execution defaults. 2641 The default cluster-scheduler is single-queue multi-server, which provides automatic load-balancing of threads on processors. 2642 However, the design allows changing the scheduler, \eg multi-queue multi-server with work-stealing/sharing across the virtual processors. 2643 If several clusters exist, both threads and virtual processors, can be explicitly migrated from one cluster to another. 2644 No automatic load balancing among clusters is performed by \CFA. 2645 2646 When a \CFA program begins execution, it creates a user cluster with a single processor and a special processor to handle preemption that does not execute user threads. 2647 The user cluster is created to contain the application user-threads. 2648 Having all threads execute on the one cluster often maximizes utilization of processors, which minimizes runtime. 2649 However, because of limitations of scheduling requirements (real-time), NUMA architecture, heterogeneous hardware, or issues with the underlying operating system, multiple clusters are sometimes necessary. 2650 2651 2652 \subsection{Virtual Processor} 2653 \label{s:RuntimeStructureProcessor} 2654 2655 A virtual processor is implemented by a kernel thread (\eg UNIX process), which are scheduled for execution on a hardware processor by the underlying operating system. 2656 Programs may use more virtual processors than hardware processors. 2657 On a multiprocessor, kernel threads are distributed across the hardware processors resulting in virtual processors executing in parallel. 2658 (It is possible to use affinity to lock a virtual processor onto a particular hardware processor~\cite{affinityLinux, affinityWindows, affinityFreebsd, affinityNetbsd, affinityMacosx}, which is used when caching issues occur or for heterogeneous hardware processors.) 2659 The \CFA runtime attempts to block unused processors and unblock processors as the system load increases; 2660 balancing the workload with processors is difficult because it requires future knowledge, \ie what will the applicaton workload do next. 2661 Preemption occurs on virtual processors rather than user threads, via operating-system interrupts. 2662 Thus virtual processors execute user threads, where preemption frequency applies to a virtual processor, so preemption occurs randomly across the executed user threads. 2663 Turning off preemption transforms user threads into fibres. 2664 2665 2666 \begin{comment} 2667 \section{Implementation} 2668 \label{s:Implementation} 2669 2670 A primary implementation challenge is avoiding contention from dynamically allocating memory because of bulk acquire, \eg the internal-scheduling design is (almost) free of allocations. 2671 All blocking operations are made by parking threads onto queues, therefore all queues are designed with intrusive nodes, where each node has preallocated link fields for chaining. 2672 Furthermore, several bulk-acquire operations need a variable amount of memory. 2673 This storage is allocated at the base of a thread's stack before blocking, which means programmers must add a small amount of extra space for stacks. 2674 2675 In \CFA, ordering of monitor acquisition relies on memory ordering to prevent deadlock~\cite{Havender68}, because all objects have distinct non-overlapping memory layouts, and mutual-exclusion for a monitor is only defined for its lifetime. 2676 When a mutex call is made, pointers to the concerned monitors are aggregated into a variable-length array and sorted. 2677 This array persists for the entire duration of the mutual exclusion and is used extensively for synchronization operations. 2678 2679 To improve performance and simplicity, context switching occurs inside a function call, so only callee-saved registers are copied onto the stack and then the stack register is switched; 2680 the corresponding registers are then restored for the other context. 2681 Note, the instruction pointer is untouched since the context switch is always inside the same function. 2682 Experimental results (not presented) for a stackless or stackful scheduler (1 versus 2 context switches) (see Section~\ref{s:Concurrency}) show the performance is virtually equivalent, because both approaches are dominated by locking to prevent a race condition. 2683 2684 All kernel threads (@pthreads@) created a stack. 2685 Each \CFA virtual processor is implemented as a coroutine and these coroutines run directly on the kernel-thread stack, effectively stealing this stack. 2686 The exception to this rule is the program main, \ie the initial kernel thread that is given to any program. 2687 In order to respect C expectations, the stack of the initial kernel thread is used by program main rather than the main processor, allowing it to grow dynamically as in a normal C program. 2688 \end{comment} 2689 2690 2691 \subsection{Preemption} 2692 2693 Nondeterministic preemption provides fairness from long-running threads, and forces concurrent programmers to write more robust programs, rather than relying on code between cooperative scheduling to be atomic. 2694 This atomic reliance can fail on multi-core machines, because execution across cores is nondeterministic. 2695 A different reason for not supporting preemption is that it significantly complicates the runtime system, \eg Microsoft runtime does not support interrupts and on Linux systems, interrupts are complex (see below). 2696 Preemption is normally handled by setting a countdown timer on each virtual processor. 2697 When the timer expires, an interrupt is delivered, and the interrupt handler resets the countdown timer, and if the virtual processor is executing in user code, the signal handler performs a user-level context-switch, or if executing in the language runtime kernel, the preemption is ignored or rolled forward to the point where the runtime kernel context switches back to user code. 2698 Multiple signal handlers may be pending. 2699 When control eventually switches back to the signal handler, it returns normally, and execution continues in the interrupted user thread, even though the return from the signal handler may be on a different kernel thread than the one where the signal is delivered. 2700 The only issue with this approach is that signal masks from one kernel thread may be restored on another as part of returning from the signal handler; 2701 therefore, the same signal mask is required for all virtual processors in a cluster. 2702 Because preemption frequency is usually long (1 millisecond) performance cost is negligible. 2703 2704 Linux switched a decade ago from specific to arbitrary process signal-delivery for applications with multiple kernel threads. 2705 \begin{cquote} 2706 A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked. 2707 If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread to which it will deliver the signal. 2708 SIGNAL(7) - Linux Programmer's Manual 2709 \end{cquote} 2710 Hence, the timer-expiry signal, which is generated \emph{externally} by the Linux kernel to an application, is delivered to any of its Linux subprocesses (kernel threads). 2711 To ensure each virtual processor receives a preemption signal, a discrete-event simulation is run on a special virtual processor, and only it sets and receives timer events. 2712 Virtual processors register an expiration time with the discrete-event simulator, which is inserted in sorted order. 2713 The simulation sets the countdown timer to the value at the head of the event list, and when the timer expires, all events less than or equal to the current time are processed. 2714 Processing a preemption event sends an \emph{internal} @SIGUSR1@ signal to the registered virtual processor, which is always delivered to that processor. 2715 2716 2717 \subsection{Debug Kernel} 2718 2719 There are two versions of the \CFA runtime kernel: debug and non-debug. 2720 The debugging version has many runtime checks and internal assertions, \eg stack (non-writable) guard page, and checks for stack overflow whenever context switches occur among coroutines and threads, which catches most stack overflows. 2721 After a program is debugged, the non-debugging version can be used to significantly decrease space and increase performance. 2722 2723 2724 \section{Performance} 2725 \label{s:Performance} 2726 2727 To verify the implementation of the \CFA runtime, a series of microbenchmarks are performed comparing \CFA with pthreads, Java OpenJDK-9, Go 1.12.6 and \uC 7.0.0. 2728 For comparison, the package must be multi-processor (M:N), which excludes libdill/libmil~\cite{libdill} (M:1)), and use a shared-memory programming model, \eg not message passing. 2729 The benchmark computer is an AMD Opteron\texttrademark\ 6380 NUMA 64-core, 8 socket, 2.5 GHz processor, running Ubuntu 16.04.6 LTS, and \CFA/\uC are compiled with gcc 6.5. 2730 2731 All benchmarks are run using the following harness. (The Java harness is augmented to circumvent JIT issues.) 2732 \begin{cfa} 2733 unsigned int N = 10_000_000; 2734 #define BENCH( `run` ) Time before = getTimeNsec(); `run;` Duration result = (getTimeNsec() - before) / N; 2735 \end{cfa} 2736 The method used to get time is @clock_gettime( CLOCK_REALTIME )@. 2737 Each benchmark is performed @N@ times, where @N@ varies depending on the benchmark; 2738 the total time is divided by @N@ to obtain the average time for a benchmark. 2739 Each benchmark experiment is run 31 times. 2740 All omitted tests for other languages are functionally identical to the \CFA tests and available online~\cite{CforallBenchMarks}. 2741 % tar --exclude=.deps --exclude=Makefile --exclude=Makefile.in --exclude=c.c --exclude=cxx.cpp --exclude=fetch_add.c -cvhf benchmark.tar benchmark 2742 2743 \paragraph{Object Creation} 2744 2745 Object creation is measured by creating/deleting the specific kind of concurrent object. 2746 Figure~\ref{f:creation} shows the code for \CFA, with results in Table~\ref{tab:creation}. 2747 The only note here is that the call stacks of \CFA coroutines are lazily created, therefore without priming the coroutine to force stack creation, the creation cost is artificially low. 2748 2749 \begin{multicols}{2} 2750 \lstset{language=CFA,moredelim=**[is][\color{red}]{@}{@},deletedelim=**[is][]{`}{`}} 2751 \begin{cfa} 2752 @thread@ MyThread {}; 2753 void @main@( MyThread & ) {} 2754 int main() { 2755 BENCH( for ( N ) { @MyThread m;@ } ) 2756 sout | result`ns; 2757 } 2758 \end{cfa} 2759 \captionof{figure}{\CFA object-creation benchmark} 2760 \label{f:creation} 2761 2762 \columnbreak 2763 2764 \vspace*{-16pt} 2765 \captionof{table}{Object creation comparison (nanoseconds)} 2766 \label{tab:creation} 2767 2768 \begin{tabular}[t]{@{}r*{3}{D{.}{.}{5.2}}@{}} 2769 \multicolumn{1}{@{}c}{} & \multicolumn{1}{c}{Median} & \multicolumn{1}{c}{Average} & \multicolumn{1}{c@{}}{Std Dev} \\ 2770 \CFA Coroutine Lazy & 13.2 & 13.1 & 0.44 \\ 2771 \CFA Coroutine Eager & 531.3 & 536.0 & 26.54 \\ 2772 \CFA Thread & 2074.9 & 2066.5 & 170.76 \\ 2773 \uC Coroutine & 89.6 & 90.5 & 1.83 \\ 2774 \uC Thread & 528.2 & 528.5 & 4.94 \\ 2775 Goroutine & 4068.0 & 4113.1 & 414.55 \\ 2776 Java Thread & 103848.5 & 104295.4 & 2637.57 \\ 2777 Pthreads & 33112.6 & 33127.1 & 165.90 3159 \begin{table} 3160 \begin{center} 3161 \begin{tabular}{| l | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] |} 3162 \cline{2-4} 3163 \multicolumn{1}{c |}{} & \multicolumn{1}{c |}{ Median } &\multicolumn{1}{c |}{ Average } & \multicolumn{1}{c |}{ Standard Deviation} \\ 3164 \hline 3165 \uC @Accept@ & 350 & 350.61 & 3.11 \\ 3166 \CFA @waitfor@, 1 @monitor@ & 358.5 & 358.36 & 3.82 \\ 3167 \CFA @waitfor@, 2 @monitor@ & 422 & 426.79 & 7.95 \\ 3168 \CFA @waitfor@, 4 @monitor@ & 579.5 & 585.46 & 11.25 \\ 3169 \hline 2778 3170 \end{tabular} 2779 \end{multicols} 2780 2781 2782 \paragraph{Context-Switching} 2783 2784 In procedural programming, the cost of a function call is important as modularization (refactoring) increases. 2785 (In many cases, a compiler inlines function calls to eliminate this cost.) 2786 Similarly, when modularization extends to coroutines/tasks, the time for a context switch becomes a relevant factor. 2787 The coroutine test is from resumer to suspender and from suspender to resumer, which is two context switches. 2788 The thread test is using yield to enter and return from the runtime kernel, which is two context switches. 2789 The difference in performance between coroutine and thread context-switch is the cost of scheduling for threads, whereas coroutines are self-scheduling. 2790 Figure~\ref{f:ctx-switch} only shows the \CFA code for coroutines/threads (other systems are similar) with all results in Table~\ref{tab:ctx-switch}. 2791 2792 \begin{multicols}{2} 2793 \lstset{language=CFA,moredelim=**[is][\color{red}]{@}{@},deletedelim=**[is][]{`}{`}} 2794 \begin{cfa}[aboveskip=0pt,belowskip=0pt] 2795 @coroutine@ C {} c; 2796 void main( C & ) { for ( ;; ) { @suspend;@ } } 2797 int main() { // coroutine test 2798 BENCH( for ( N ) { @resume( c );@ } ) 2799 sout | result`ns; 2800 } 2801 int main() { // task test 2802 BENCH( for ( N ) { @yield();@ } ) 2803 sout | result`ns; 2804 } 2805 \end{cfa} 2806 \captionof{figure}{\CFA context-switch benchmark} 2807 \label{f:ctx-switch} 2808 2809 \columnbreak 2810 2811 \vspace*{-16pt} 2812 \captionof{table}{Context switch comparison (nanoseconds)} 2813 \label{tab:ctx-switch} 2814 \begin{tabular}{@{}r*{3}{D{.}{.}{3.2}}@{}} 2815 \multicolumn{1}{@{}c}{} & \multicolumn{1}{c}{Median} &\multicolumn{1}{c}{Average} & \multicolumn{1}{c@{}}{Std Dev} \\ 2816 C function & 1.8 & 1.8 & 0.01 \\ 2817 \CFA generator & 2.4 & 2.2 & 0.25 \\ 2818 \CFA Coroutine & 36.2 & 36.2 & 0.25 \\ 2819 \CFA Thread & 93.2 & 93.5 & 2.09 \\ 2820 \uC Coroutine & 52.0 & 52.1 & 0.51 \\ 2821 \uC Thread & 96.2 & 96.3 & 0.58 \\ 2822 Goroutine & 141.0 & 141.3 & 3.39 \\ 2823 Java Thread & 374.0 & 375.8 & 10.38 \\ 2824 Pthreads Thread & 361.0 & 365.3 & 13.19 2825 \end{tabular} 2826 \end{multicols} 2827 2828 2829 \paragraph{Mutual-Exclusion} 2830 2831 Uncontented mutual exclusion, which frequently occurs, is measured by entering/leaving a critical section. 2832 For monitors, entering and leaving a monitor function is measured. 2833 To put the results in context, the cost of entering a non-inline function and the cost of acquiring and releasing a @pthread_mutex@ lock is also measured. 2834 Figure~\ref{f:mutex} shows the code for \CFA with all results in Table~\ref{tab:mutex}. 2835 Note, the incremental cost of bulk acquire for \CFA, which is largely a fixed cost for small numbers of mutex objects. 2836 2837 \begin{multicols}{2} 2838 \lstset{language=CFA,moredelim=**[is][\color{red}]{@}{@},deletedelim=**[is][]{`}{`}} 2839 \begin{cfa} 2840 @monitor@ M {} m1/*, m2, m3, m4*/; 2841 void __attribute__((noinline)) 2842 do_call( M & @mutex m/*, m2, m3, m4*/@ ) {} 3171 \end{center} 3172 \caption{External scheduling comparison. 3173 All numbers are in nanoseconds(\si{\nano\second})} 3174 \label{tab:ext-sched} 3175 \end{table} 3176 3177 3178 \subsection{Object Creation} 3179 Finally, the last benchmark measures the cost of creation for concurrent objects. 3180 Figure~\ref{f:creation} shows the code for @pthread@s and \CFA threads, with results shown in table \ref{tab:creation}. 3181 As with all other benchmarks, all omitted tests are functionally identical to one of these tests. 3182 The only note here is that the call stacks of \CFA coroutines are lazily created, therefore without priming the coroutine, the creation cost is very low. 3183 3184 \begin{figure} 3185 \begin{center} 3186 @pthread@ 3187 \begin{cfa} 2843 3188 int main() { 2844 3189 BENCH( 2845 for( N ) do_call( m1/*, m2, m3, m4*/ ); 3190 for(size_t i=0; i<n; i++) { 3191 pthread_t thread; 3192 if(pthread_create(&thread,NULL,foo,NULL)<0) { 3193 perror( "failure" ); 3194 return 1; 3195 } 3196 3197 if(pthread_join(thread, NULL)<0) { 3198 perror( "failure" ); 3199 return 1; 3200 } 3201 }, 3202 result 2846 3203 ) 2847 sout | result`ns; 2848 } 2849 \end{cfa} 2850 \captionof{figure}{\CFA acquire/release mutex benchmark} 2851 \label{f:mutex} 2852 2853 \columnbreak 2854 2855 \vspace*{-16pt} 2856 \captionof{table}{Mutex comparison (nanoseconds)} 2857 \label{tab:mutex} 2858 \begin{tabular}{@{}r*{3}{D{.}{.}{3.2}}@{}} 2859 \multicolumn{1}{@{}c}{} & \multicolumn{1}{c}{Median} &\multicolumn{1}{c}{Average} & \multicolumn{1}{c@{}}{Std Dev} \\ 2860 test and test-and-test lock & 19.1 & 18.9 & 0.40 \\ 2861 \CFA @mutex@ function, 1 arg. & 45.9 & 46.6 & 1.45 \\ 2862 \CFA @mutex@ function, 2 arg. & 105.0 & 104.7 & 3.08 \\ 2863 \CFA @mutex@ function, 4 arg. & 165.0 & 167.6 & 5.65 \\ 2864 \uC @monitor@ member rtn. & 54.0 & 53.7 & 0.82 \\ 2865 Java synchronized method & 31.0 & 31.1 & 0.50 \\ 2866 Pthreads Mutex Lock & 33.6 & 32.6 & 1.14 3204 printf("%llu\n", result); 3205 } 3206 \end{cfa} 3207 3208 3209 3210 \CFA Threads 3211 \begin{cfa} 3212 int main() { 3213 BENCH( 3214 for(size_t i=0; i<n; i++) { 3215 MyThread m; 3216 }, 3217 result 3218 ) 3219 printf("%llu\n", result); 3220 } 3221 \end{cfa} 3222 \end{center} 3223 \caption{Benchmark code for \protect\lstinline|pthread|s and \CFA to measure object creation} 3224 \label{f:creation} 3225 \end{figure} 3226 3227 \begin{table} 3228 \begin{center} 3229 \begin{tabular}{| l | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] | S[table-format=5.2,table-number-alignment=right] |} 3230 \cline{2-4} 3231 \multicolumn{1}{c |}{} & \multicolumn{1}{c |}{ Median } &\multicolumn{1}{c |}{ Average } & \multicolumn{1}{c |}{ Standard Deviation} \\ 3232 \hline 3233 Pthreads & 26996 & 26984.71 & 156.6 \\ 3234 \CFA Coroutine Lazy & 6 & 5.71 & 0.45 \\ 3235 \CFA Coroutine Eager & 708 & 706.68 & 4.82 \\ 3236 \CFA Thread & 1173.5 & 1176.18 & 15.18 \\ 3237 \uC Coroutine & 109 & 107.46 & 1.74 \\ 3238 \uC Thread & 526 & 530.89 & 9.73 \\ 3239 Goroutine & 2520.5 & 2530.93 & 61,56 \\ 3240 Java Thread & 91114.5 & 92272.79 & 961.58 \\ 3241 \hline 2867 3242 \end{tabular} 2868 \end{multicols} 2869 2870 2871 \paragraph{External Scheduling} 2872 2873 External scheduling is measured using a cycle of two threads calling and accepting the call using the @waitfor@ statement. 2874 Figure~\ref{f:ext-sched} shows the code for \CFA, with results in Table~\ref{tab:ext-sched}. 2875 Note, the incremental cost of bulk acquire for \CFA, which is largely a fixed cost for small numbers of mutex objects. 2876 2877 \begin{multicols}{2} 2878 \lstset{language=CFA,moredelim=**[is][\color{red}]{@}{@},deletedelim=**[is][]{`}{`}} 2879 \vspace*{-16pt} 2880 \begin{cfa} 2881 volatile int go = 0; 2882 @monitor@ M {} m; 2883 thread T {}; 2884 void __attribute__((noinline)) 2885 do_call( M & @mutex@ ) {} 2886 void main( T & ) { 2887 while ( go == 0 ) { yield(); } 2888 while ( go == 1 ) { do_call( m ); } 2889 } 2890 int __attribute__((noinline)) 2891 do_wait( M & @mutex@ m ) { 2892 go = 1; // continue other thread 2893 BENCH( for ( N ) { @waitfor( do_call, m );@ } ) 2894 go = 0; // stop other thread 2895 sout | result`ns; 2896 } 2897 int main() { 2898 T t; 2899 do_wait( m ); 2900 } 2901 \end{cfa} 2902 \captionof{figure}{\CFA external-scheduling benchmark} 2903 \label{f:ext-sched} 2904 2905 \columnbreak 2906 2907 \vspace*{-16pt} 2908 \captionof{table}{External-scheduling comparison (nanoseconds)} 2909 \label{tab:ext-sched} 2910 \begin{tabular}{@{}r*{3}{D{.}{.}{3.2}}@{}} 2911 \multicolumn{1}{@{}c}{} & \multicolumn{1}{c}{Median} &\multicolumn{1}{c}{Average} & \multicolumn{1}{c@{}}{Std Dev} \\ 2912 \CFA @waitfor@, 1 @monitor@ & 376.4 & 376.8 & 7.63 \\ 2913 \CFA @waitfor@, 2 @monitor@ & 491.4 & 492.0 & 13.31 \\ 2914 \CFA @waitfor@, 4 @monitor@ & 681.0 & 681.7 & 19.10 \\ 2915 \uC @_Accept@ & 331.1 & 331.4 & 2.66 2916 \end{tabular} 2917 \end{multicols} 2918 2919 2920 \paragraph{Internal Scheduling} 2921 2922 Internal scheduling is measured using a cycle of two threads signalling and waiting. 2923 Figure~\ref{f:int-sched} shows the code for \CFA, with results in Table~\ref{tab:int-sched}. 2924 Note, the incremental cost of bulk acquire for \CFA, which is largely a fixed cost for small numbers of mutex objects. 2925 Java scheduling is significantly greater because the benchmark explicitly creates multiple thread in order to prevent the JIT from making the program sequential, \ie removing all locking. 2926 2927 \begin{multicols}{2} 2928 \lstset{language=CFA,moredelim=**[is][\color{red}]{@}{@},deletedelim=**[is][]{`}{`}} 2929 \begin{cfa} 2930 volatile int go = 0; 2931 @monitor@ M { @condition c;@ } m; 2932 void __attribute__((noinline)) 2933 do_call( M & @mutex@ a1 ) { @signal( c );@ } 2934 thread T {}; 2935 void main( T & this ) { 2936 while ( go == 0 ) { yield(); } 2937 while ( go == 1 ) { do_call( m ); } 2938 } 2939 int __attribute__((noinline)) 2940 do_wait( M & mutex m ) with(m) { 2941 go = 1; // continue other thread 2942 BENCH( for ( N ) { @wait( c );@ } ); 2943 go = 0; // stop other thread 2944 sout | result`ns; 2945 } 2946 int main() { 2947 T t; 2948 do_wait( m ); 2949 } 2950 \end{cfa} 2951 \captionof{figure}{\CFA Internal-scheduling benchmark} 2952 \label{f:int-sched} 2953 2954 \columnbreak 2955 2956 \vspace*{-16pt} 2957 \captionof{table}{Internal-scheduling comparison (nanoseconds)} 2958 \label{tab:int-sched} 2959 \bigskip 2960 2961 \begin{tabular}{@{}r*{3}{D{.}{.}{5.2}}@{}} 2962 \multicolumn{1}{@{}c}{} & \multicolumn{1}{c}{Median} & \multicolumn{1}{c}{Average} & \multicolumn{1}{c@{}}{Std Dev} \\ 2963 \CFA @signal@, 1 @monitor@ & 372.6 & 374.3 & 14.17 \\ 2964 \CFA @signal@, 2 @monitor@ & 492.7 & 494.1 & 12.99 \\ 2965 \CFA @signal@, 4 @monitor@ & 749.4 & 750.4 & 24.74 \\ 2966 \uC @signal@ & 320.5 & 321.0 & 3.36 \\ 2967 Java @notify@ & 10160.5 & 10169.4 & 267.71 \\ 2968 Pthreads Cond. Variable & 4949.6 & 5065.2 & 363 2969 \end{tabular} 2970 \end{multicols} 3243 \end{center} 3244 \caption{Creation comparison. 3245 All numbers are in nanoseconds(\si{\nano\second}).} 3246 \label{tab:creation} 3247 \end{table} 3248 2971 3249 2972 3250 2973 3251 \section{Conclusion} 2974 2975 Advanced control-flow will always be difficult, especially when there is temporal ordering and nondeterminism. 2976 However, many systems exacerbate the difficulty through their presentation mechanisms. 2977 This paper shows it is possible to present a hierarchy of control-flow features, generator, coroutine, thread, and monitor, providing an integrated set of high-level, efficient, and maintainable control-flow features. 2978 Eliminated from \CFA are spurious wakeup and barging, which are nonintuitive and lead to errors, and having to work with a bewildering set of low-level locks and acquisition techniques. 2979 \CFA high-level race-free monitors and tasks provide the core mechanisms for mutual exclusion and synchronization, without having to resort to magic qualifiers like @volatile@/@atomic@. 2980 Extending these mechanisms to handle high-level deadlock-free bulk acquire across both mutual exclusion and synchronization is a unique contribution. 2981 The \CFA runtime provides concurrency based on a preemptive M:N user-level threading-system, executing in clusters, which encapsulate scheduling of work on multiple kernel threads providing parallelism. 2982 The M:N model is judged to be efficient and provide greater flexibility than a 1:1 threading model. 2983 These concepts and the \CFA runtime-system are written in the \CFA language, extensively leveraging the \CFA type-system, which demonstrates the expressiveness of the \CFA language. 2984 Performance comparisons with other concurrent systems/languages show the \CFA approach is competitive across all low-level operations, which translates directly into good performance in well-written concurrent applications. 2985 C programmers should feel comfortable using these mechanisms for developing complex control-flow in applications, with the ability to obtain maximum available performance by selecting mechanisms at the appropriate level of need. 2986 2987 3252 This paper has achieved a minimal concurrency \textbf{api} that is simple, efficient and usable as the basis for higher-level features. 3253 The approach presented is based on a lightweight thread-system for parallelism, which sits on top of clusters of processors. 3254 This M:N model is judged to be both more efficient and allow more flexibility for users. 3255 Furthermore, this document introduces monitors as the main concurrency tool for users. 3256 This paper also offers a novel approach allowing multiple monitors to be accessed simultaneously without running into the Nested Monitor Problem~\cite{Lister77}. 3257 It also offers a full implementation of the concurrency runtime written entirely in \CFA, effectively the largest \CFA code base to date. 3258 3259 3260 % ====================================================================== 3261 % ====================================================================== 2988 3262 \section{Future Work} 2989 2990 While control flow in \CFA has a strong start, development is still underway to complete a number of missing features. 2991 2992 \paragraph{Flexible Scheduling} 2993 \label{futur:sched} 2994 3263 % ====================================================================== 3264 % ====================================================================== 3265 3266 \subsection{Performance} \label{futur:perf} 3267 This paper presents a first implementation of the \CFA concurrency runtime. 3268 Therefore, there is still significant work to improve performance. 3269 Many of the data structures and algorithms may change in the future to more efficient versions. 3270 For example, the number of monitors in a single bulk acquire is only bound by the stack size, this is probably unnecessarily generous. 3271 It may be possible that limiting the number helps increase performance. 3272 However, it is not obvious that the benefit would be significant. 3273 3274 \subsection{Flexible Scheduling} \label{futur:sched} 2995 3275 An important part of concurrency is scheduling. 2996 3276 Different scheduling algorithms can affect performance (both in terms of average and variation). 2997 3277 However, no single scheduler is optimal for all workloads and therefore there is value in being able to change the scheduler for given programs. 2998 One solution is to offer various tuning options, allowing the scheduler to be adjusted to the requirements of the workload. 2999 However, to be truly flexible, a pluggable scheduler is necessary. 3000 Currently, the \CFA pluggable scheduler is too simple to handle complex scheduling, \eg quality of service and real-time, where the scheduler must interact with mutex objects to deal with issues like priority inversion~\cite{Buhr00b}. 3001 3002 \paragraph{Non-Blocking I/O} 3003 \label{futur:nbio} 3004 3005 Many modern workloads are not bound by computation but IO operations, a common case being web servers and XaaS~\cite{XaaS} (anything as a service). 3006 These types of workloads require significant engineering to amortizing costs of blocking IO-operations. 3007 At its core, non-blocking I/O is an operating-system level feature queuing IO operations, \eg network operations, and registering for notifications instead of waiting for requests to complete. 3008 Current trends use asynchronous programming like callbacks, futures, and/or promises, \eg Node.js~\cite{NodeJs} for JavaScript, Spring MVC~\cite{SpringMVC} for Java, and Django~\cite{Django} for Python. 3009 However, these solutions lead to code that is hard to create, read and maintain. 3010 A better approach is to tie non-blocking I/O into the concurrency system to provide ease of use with low overhead, \eg thread-per-connection web-services. 3011 A non-blocking I/O library is currently under development for \CFA. 3012 3013 \paragraph{Other Concurrency Tools} 3014 \label{futur:tools} 3015 3016 While monitors offer flexible and powerful concurrency for \CFA, other concurrency tools are also necessary for a complete multi-paradigm concurrency package. 3017 Examples of such tools can include futures and promises~\cite{promises}, executors and actors. 3018 These additional features are useful for applications that can be constructed without shared data and direct blocking. 3019 As well, new \CFA extensions should make it possible to create a uniform interface for virtually all mutual exclusion, including monitors and low-level locks. 3020 3021 \paragraph{Implicit Threading} 3022 \label{futur:implcit} 3023 3024 Basic concurrent (embarrassingly parallel) applications can benefit greatly from implicit concurrency, where sequential programs are converted to concurrent, possibly with some help from pragmas to guide the conversion. 3025 This type of concurrency can be achieved both at the language level and at the library level. 3026 The canonical example of implicit concurrency is concurrent nested @for@ loops, which are amenable to divide and conquer algorithms~\cite{uC++book}. 3027 The \CFA language features should make it possible to develop a reasonable number of implicit concurrency mechanism to solve basic HPC data-concurrency problems. 3028 However, implicit concurrency is a restrictive solution with significant limitations, so it can never replace explicit concurrent programming. 3029 3030 3278 One solution is to offer various tweaking options to users, allowing the scheduler to be adjusted to the requirements of the workload. 3279 However, in order to be truly flexible, it would be interesting to allow users to add arbitrary data and arbitrary scheduling algorithms. 3280 For example, a web server could attach Type-of-Service information to threads and have a ``ToS aware'' scheduling algorithm tailored to this specific web server. 3281 This path of flexible schedulers will be explored for \CFA. 3282 3283 \subsection{Non-Blocking I/O} \label{futur:nbio} 3284 While most of the parallelism tools are aimed at data parallelism and control-flow parallelism, many modern workloads are not bound on computation but on IO operations, a common case being web servers and XaaS (anything as a service). 3285 These types of workloads often require significant engineering around amortizing costs of blocking IO operations. 3286 At its core, non-blocking I/O is an operating system level feature that allows queuing IO operations (\eg network operations) and registering for notifications instead of waiting for requests to complete. 3287 In this context, the role of the language makes Non-Blocking IO easily available and with low overhead. 3288 The current trend is to use asynchronous programming using tools like callbacks and/or futures and promises, which can be seen in frameworks like Node.js~\cite{NodeJs} for JavaScript, Spring MVC~\cite{SpringMVC} for Java and Django~\cite{Django} for Python. 3289 However, while these are valid solutions, they lead to code that is harder to read and maintain because it is much less linear. 3290 3291 \subsection{Other Concurrency Tools} \label{futur:tools} 3292 While monitors offer a flexible and powerful concurrent core for \CFA, other concurrency tools are also necessary for a complete multi-paradigm concurrency package. 3293 Examples of such tools can include simple locks and condition variables, futures and promises~\cite{promises}, executors and actors. 3294 These additional features are useful when monitors offer a level of abstraction that is inadequate for certain tasks. 3295 3296 \subsection{Implicit Threading} \label{futur:implcit} 3297 Simpler applications can benefit greatly from having implicit parallelism. 3298 That is, parallelism that does not rely on the user to write concurrency. 3299 This type of parallelism can be achieved both at the language level and at the library level. 3300 The canonical example of implicit parallelism is parallel for loops, which are the simplest example of a divide and conquer algorithms~\cite{uC++book}. 3301 Table \ref{f:parfor} shows three different code examples that accomplish point-wise sums of large arrays. 3302 Note that none of these examples explicitly declare any concurrency or parallelism objects. 3303 3304 \begin{table} 3305 \begin{center} 3306 \begin{tabular}[t]{|c|c|c|} 3307 Sequential & Library Parallel & Language Parallel \\ 3308 \begin{cfa}[tabsize=3] 3309 void big_sum( 3310 int* a, int* b, 3311 int* o, 3312 size_t len) 3313 { 3314 for( 3315 int i = 0; 3316 i < len; 3317 ++i ) 3318 { 3319 o[i]=a[i]+b[i]; 3320 } 3321 } 3322 3323 3324 3325 3326 3327 int* a[10000]; 3328 int* b[10000]; 3329 int* c[10000]; 3330 //... fill in a & b 3331 big_sum(a,b,c,10000); 3332 \end{cfa} &\begin{cfa}[tabsize=3] 3333 void big_sum( 3334 int* a, int* b, 3335 int* o, 3336 size_t len) 3337 { 3338 range ar(a, a+len); 3339 range br(b, b+len); 3340 range or(o, o+len); 3341 parfor( ai, bi, oi, 3342 []( int* ai, 3343 int* bi, 3344 int* oi) 3345 { 3346 oi=ai+bi; 3347 }); 3348 } 3349 3350 3351 int* a[10000]; 3352 int* b[10000]; 3353 int* c[10000]; 3354 //... fill in a & b 3355 big_sum(a,b,c,10000); 3356 \end{cfa}&\begin{cfa}[tabsize=3] 3357 void big_sum( 3358 int* a, int* b, 3359 int* o, 3360 size_t len) 3361 { 3362 parfor (ai,bi,oi) 3363 in (a, b, o ) 3364 { 3365 oi = ai + bi; 3366 } 3367 } 3368 3369 3370 3371 3372 3373 3374 3375 int* a[10000]; 3376 int* b[10000]; 3377 int* c[10000]; 3378 //... fill in a & b 3379 big_sum(a,b,c,10000); 3380 \end{cfa} 3381 \end{tabular} 3382 \end{center} 3383 \caption{For loop to sum numbers: Sequential, using library parallelism and language parallelism.} 3384 \label{f:parfor} 3385 \end{table} 3386 3387 Implicit parallelism is a restrictive solution and therefore has its limitations. 3388 However, it is a quick and simple approach to parallelism, which may very well be sufficient for smaller applications and reduces the amount of boilerplate needed to start benefiting from parallelism in modern CPUs. 3389 3390 3391 % A C K N O W L E D G E M E N T S 3392 % ------------------------------- 3031 3393 \section{Acknowledgements} 3032 3394 3033 The authors would like to recognize the design assistance of Aaron Moss, Rob Schluntz, Andrew Beach and Michael Brooks on the features described in this paper. 3034 Funding for this project has been provided by Huawei Ltd.\ (\url{http://www.huawei.com}). %, and Peter Buhr is partially funded by the Natural Sciences and Engineering Research Council of Canada. 3035 3036 {% 3037 \fontsize{9bp}{12bp}\selectfont% 3395 Thanks to Aaron Moss, Rob Schluntz and Andrew Beach for their work on the \CFA project as well as all the discussions which helped concretize the ideas in this paper. 3396 Partial funding was supplied by the Natural Sciences and Engineering Research Council of Canada and a corporate partnership with Huawei Ltd. 3397 3398 3399 % B I B L I O G R A P H Y 3400 % ----------------------------- 3401 %\bibliographystyle{plain} 3038 3402 \bibliography{pl,local} 3039 }% 3403 3040 3404 3041 3405 \end{document}
Note:
See TracChangeset
for help on using the changeset viewer.