132
KC ++ – A Concurrent C ++ Programming System Matti Rintala Software Systems Laboratory Tampere University of Technology

KC++ – A Concurrent C++ Programming Systembitti/files/kcpp-thesis.pdf · ABSTRACT Abstract Object-oriented programming and concurrent programming are both becoming more and more

Embed Size (px)

Citation preview

KC++ – A Concurrent C++Programming System

Matti Rintala

Software Systems LaboratoryTampere University of Technology

ABSTRACT

Abstract

Object-oriented programming and concurrent programming are both becoming moreand more common. During the last decade, there has been a lot of research aiming tocombine the two, either in the form of a new programming language, a class library,or an extension to an existing language.

This thesis presents KC++ — a concurrent object-oriented programming system.The KC++ system is based on the C++ language, and it introduces concurrency usingactive objects, asynchronous method calls, and futures. It does this without extendingthe syntax of C++, adding concurrent semantics to existing C++ concepts. The KC++system consists of a KC++-to-C++ compiler and a class library.

The main design goal of the system is to make programming with active objectsas close as possible to programming with normal C++ objects, without restrictionson how active objects can be created or used. Most existing C++ programming styles,idioms and design patterns should be directly usable in KC++. Existing C++ style ana-lysers and debugging tools can be used with KC++ without modifications.

i

PREFACE

Preface

The ideas behind this thesis were formed during my stay at King’s College Londonduring 1998. Most of all I want to thank my supervisor Prof. Russel Winder, whosesuggestions and knowledge on concurrent C++-based programming helped me enorm-ously. I’d also like to thank everybody else at King’s for making me feel at home there.

I thank Prof. Kai Koskimies for tutoring me after I returned to Tampere Univer-sity of Technology, and for reviewing the thesis. Likewise I thank Juha Vihavainenfrom Helsinki University for his help as the second reviewer and his suggestions forimprovements.

Thanks are also in order to Jyke and everyone else at the Software Systems Lab,especially for their general patience and answers to my trivial problems.

But most importantly I want to thank Emma for her support and comfort on thosedark hours after midnight, when the whole project seemed like a Torment [Inter-play, 1999]

ii

CONTENTS

Contents

1 Introduction 1

1.1 Need for concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

1.2 A language extension vs. a library . . . . . . . . . . . . . . . . . . . . . 2

1.3 Active objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

1.4 Motivation behind KC++ . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.5 The history of KC++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.6 The structure of this thesis . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Overview of KC++ 7

2.1 The KC++ active object concept . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.1 Active classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.2 Creating active objects . . . . . . . . . . . . . . . . . . . . . . . 8

2.1.3 Proxies and reference counting . . . . . . . . . . . . . . . . . . 9

2.2 Futures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.2.1 Normal futures . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2.2.2 void-futures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2.2.3 Future sources . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

2.3 Passing parameters to active objects . . . . . . . . . . . . . . . . . . . . 13

2.3.1 Normal parameters . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.3.2 Future parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.3.3 Future reference parameters . . . . . . . . . . . . . . . . . . . . 14

2.3.4 Active object parameters . . . . . . . . . . . . . . . . . . . . . . 15

2.4 Locking active objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3 Active objects and proxies 17

3.1 Making objects active . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

iii

CONTENTS

3.2 Proxy classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3.2.1 Internals of a proxy . . . . . . . . . . . . . . . . . . . . . . . . . 19

3.2.2 Lifetime of an active object . . . . . . . . . . . . . . . . . . . . . 20

3.2.3 Proxies and inheritance . . . . . . . . . . . . . . . . . . . . . . . 21

3.3 Inheritance and active classes . . . . . . . . . . . . . . . . . . . . . . . 22

3.4 Calling active object methods . . . . . . . . . . . . . . . . . . . . . . . 23

3.4.1 Local method calls . . . . . . . . . . . . . . . . . . . . . . . . . . 24

3.4.2 Accessing private members of other objects . . . . . . . . . . . 25

3.5 Choosing the correct method . . . . . . . . . . . . . . . . . . . . . . . . 26

3.5.1 Basic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

3.5.2 Class types and structs . . . . . . . . . . . . . . . . . . . . . . . 28

3.5.3 Pointer and reference types . . . . . . . . . . . . . . . . . . . . . 28

3.6 Returning values from active objects . . . . . . . . . . . . . . . . . . . 30

4 Passing parameters to active objects 32

4.1 Marshalling and unmarshalling . . . . . . . . . . . . . . . . . . . . . . 32

4.2 Different types of parameters . . . . . . . . . . . . . . . . . . . . . . . . 33

4.2.1 Basic types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33

4.2.2 Non-active user-defined classes and structs . . . . . . . . . . . 33

4.2.3 Enumeration types and non-user-defined types . . . . . . . . . 34

4.2.4 Active classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4.2.5 Pointers and references to active objects . . . . . . . . . . . . . 35

4.2.6 Pointers and references to non-active types . . . . . . . . . . . . 36

4.2.7 Futures and references to futures . . . . . . . . . . . . . . . . . 36

4.3 Pointers and references to active objects . . . . . . . . . . . . . . . . . 37

4.3.1 References to active objects . . . . . . . . . . . . . . . . . . . . . 38

4.3.2 Pointers to active objects . . . . . . . . . . . . . . . . . . . . . . 42

4.4 Passing active objects by value . . . . . . . . . . . . . . . . . . . . . . . 44

5 Asynchronous method calls and futures 47

5.1 Principles of futures in KC++ . . . . . . . . . . . . . . . . . . . . . . . . 48

5.1.1 The interface of futures . . . . . . . . . . . . . . . . . . . . . . . 49

5.1.2 Using futures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

5.1.3 Futures as constant references to “system-wide” data . . . . . . 53

iv

CONTENTS

5.1.4 Creating futures . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

5.1.5 Assigning futures . . . . . . . . . . . . . . . . . . . . . . . . . . 56

5.1.6 Destroying futures . . . . . . . . . . . . . . . . . . . . . . . . . . 57

5.2 Futures as return values . . . . . . . . . . . . . . . . . . . . . . . . . . . 58

5.2.1 Asynchronous method calls . . . . . . . . . . . . . . . . . . . . 62

5.2.2 Synchronous method calls . . . . . . . . . . . . . . . . . . . . . 64

5.2.3 Explicit future return types . . . . . . . . . . . . . . . . . . . . . 64

5.3 Futures as parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

5.3.1 Futures as value parameters . . . . . . . . . . . . . . . . . . . . 67

5.3.2 Futures as reference parameters . . . . . . . . . . . . . . . . . . 68

5.4 Pure synchronisation — void-futures . . . . . . . . . . . . . . . . . . . 69

5.5 Creating futures explicitly — future sources . . . . . . . . . . . . . . . 71

5.6 Locking active objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

6 Implementation of KC++ 79

6.1 The KC++ compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

6.1.1 Active class definitions . . . . . . . . . . . . . . . . . . . . . . . 81

6.1.2 Uses of active objects, pointers and references . . . . . . . . . . 82

6.1.3 Additional changes to pointers . . . . . . . . . . . . . . . . . . . 84

6.2 The KC++ library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

6.2.1 Implementation of the base class Active . . . . . . . . . . . . . 85

6.2.2 Implementation of proxies, pointers and references . . . . . . . 85

6.2.3 Implementation of futures . . . . . . . . . . . . . . . . . . . . . 87

6.2.4 Implementation of message streams . . . . . . . . . . . . . . . . 90

6.2.5 Implementation of unmarshalling . . . . . . . . . . . . . . . . . 91

7 Related work 94

7.1 Classification of concurrent object-oriented systems . . . . . . . . . . 94

7.2 Concurrent object-oriented languages . . . . . . . . . . . . . . . . . . . 95

7.3 Concurrent object-oriented libraries . . . . . . . . . . . . . . . . . . . . 96

7.4 Concurrent extensions to C++ . . . . . . . . . . . . . . . . . . . . . . . . 97

8 Conclusions and future work 100

8.1 Advantages of KC++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

8.1.1 Familiar syntax and “feel” . . . . . . . . . . . . . . . . . . . . . 100

v

CONTENTS

8.1.2 Making old classes active . . . . . . . . . . . . . . . . . . . . . . 101

8.1.3 Ease of debugging . . . . . . . . . . . . . . . . . . . . . . . . . . 101

8.1.4 Platform independence . . . . . . . . . . . . . . . . . . . . . . . 101

8.1.5 Only one communication and synchronisation mechanism . . 102

8.2 Disadvantages of KC++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

8.2.1 The active object model and RTC . . . . . . . . . . . . . . . . . 102

8.2.2 Active object reference counting . . . . . . . . . . . . . . . . . . 103

8.2.3 No subtyping with non-active bases . . . . . . . . . . . . . . . . 103

8.2.4 The future relaying mechanism . . . . . . . . . . . . . . . . . . 104

8.3 Future work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

Bibliography 106

Appendixes: Example listings 111

A Producer–consumer buffer from non-active base 111

B Priority semaphore as an active object 115

C Producer–consumer buffer with futures 118

vi

FIGURES

Figures

2.1 The KC++ proxy reference counting model . . . . . . . . . . . . . . . . 11

3.1 Different ways of making a class active in KC++ . . . . . . . . . . . . . 18

3.2 Inheritance hierarchies of active classes and proxies . . . . . . . . . . 21

3.3 An active object method call . . . . . . . . . . . . . . . . . . . . . . . . 23

3.4 Creating the method message . . . . . . . . . . . . . . . . . . . . . . . . 27

3.5 Passing a polymorphic object . . . . . . . . . . . . . . . . . . . . . . . . 29

3.6 Returning a value from an active object method . . . . . . . . . . . . . 31

4.1 Passing an active object pointer between active objects . . . . . . . . . 44

4.2 Passing an active object by value . . . . . . . . . . . . . . . . . . . . . . 46

5.1 Different ways to implement futures . . . . . . . . . . . . . . . . . . . . 54

5.2 Destruction of a future after receiving the value . . . . . . . . . . . . . 59

5.3 Destruction of a future before receiving the value . . . . . . . . . . . . 60

6.1 The KC++ compilation process . . . . . . . . . . . . . . . . . . . . . . . 80

6.2 The KC++ future implementation . . . . . . . . . . . . . . . . . . . . . . 88

vii

CODE LISTINGS

Code listings

2.1 Creation of an active class and active objects . . . . . . . . . . . . . . . 8

2.2 Part of code generated from the code in Listing 2.1 . . . . . . . . . . . 10

4.1 Marshalling/unmarshalling mechanisms of a class . . . . . . . . . . . . 35

4.2 Marshalling/unmarshalling of an enumeration . . . . . . . . . . . . . . 36

4.3 Object references and asynchronous calls . . . . . . . . . . . . . . . . 41

5.1 Example where ints have been replaced with futures . . . . . . . . . . 52

5.2 Definitions of an active class and its proxy class . . . . . . . . . . . . . 61

5.3 Futures with ISO C++ containers . . . . . . . . . . . . . . . . . . . . . . 63

5.4 Use of explicit future return types . . . . . . . . . . . . . . . . . . . . . 66

5.5 Listing 5.4 with a non-future return type . . . . . . . . . . . . . . . . . 67

5.6 Synchronisation with void-futures . . . . . . . . . . . . . . . . . . . . . 70

5.7 Outline of an active class implementing a barrier . . . . . . . . . . . . 72

5.8 The barrier class using a future source . . . . . . . . . . . . . . . . . . 74

5.9 A reusable barrier class . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

5.10 Object locking using lock objects . . . . . . . . . . . . . . . . . . . . . 77

5.11 Lock objects as lock tokens with auto_ptr . . . . . . . . . . . . . . . . 78

6.1 Compiled active class definitions . . . . . . . . . . . . . . . . . . . . . 83

6.2 Changes to the this pointer . . . . . . . . . . . . . . . . . . . . . . . . . 85

6.3 A compiler-generated proxy method . . . . . . . . . . . . . . . . . . . . 86

6.4 The definition of SPECIAL_UNMARSHAL . . . . . . . . . . . . . . . . . . . 93

viii

CHAPTER 1. INTRODUCTION

Chapter 1

Introduction

During the last decade, object-oriented programming has ceased to be just “the latestfad” and has established its position among the respected mainstream software designparadigms. Its benefits include strong and intuitive encapsulation (if used correctly)with classes, and code reuse and limited generic programming through inheritanceand dynamic binding. It has proven its usefulness, even though it has also demon-strated that writing reusable code requires careful planning and extra work, even withobject-oriented programming.

At the moment the most widely used language for object-oriented programming isC++. Because of its historical background, suitability for system-level programmingand fame as “an efficient language,” it has become a “default language” in manycompanies and universities. C++ has gained its position despite the fact that strictlyspeaking it is not a pure object-oriented programming language, but a language “thatsupports Object-Oriented and other useful styles of programming” [Stroustrup, 2000].

1.1 Need for concurrency

While object-oriented programming has established its position in software design,need for concurrency in computer programs has increased rapidly. This need arisesfrom several directions. One obvious way to increase computing performance is touse several processors in parallel. Current computer networks and “global comput-ing” have created a need for distributed programs, where different parts of the pro-gram reside in different computers and possibly execute in parallel. Modern graphicaluser interfaces and other reactive systems often require that a program responds tooutside events seemingly concurrently, even in a one-processor environment.

For these reasons research on concurrent and parallel programming has been act-ive at the same time as object-oriented programming has become popular. It wasinevitable that the idea to combine these two would arise quite soon. However, mostobject-oriented programming languages were not originally designed with concur-

1

CHAPTER 1. INTRODUCTION

rency in mind, so many different approaches have been tried to introduce concur-rency into the world of object-oriented programming (see [Agha et al., 1993], for ex-ample).

1.2 A language extension vs. a library

Clearly one way to combine object-oriented programming and concurrency is to cre-ate a whole new programming language designed just for this purpose. However, it isvery difficult to get people to use a completely new programming language in today’sworld where even many existing small languages face extinction, i.e. to become lan-guages “which are only of academic interest.” Most software projects rely heavilyon existing libraries written in some existing programming language, and using suchlibraries from another programming language would often be painful. Programmingsupport tools like user-interface generators, visualisation tools and debuggers are alsolanguage-specific, and would have to be rewritten for a new language.

For the reasons above, most concurrent programming systems are based on anexisting language. The chosen language and its features then dictate how concurrencycan be added to the language. The subject of this thesis, KC++ (King’s College C++), isbased on the C++ language, mainly because of popularity and efficiency C++ (and thefact that its predecessor UC++ was also based on C++).

There are several ways to add support for concurrency to a programming lan-guage like C++, and many such additions are presented in [Wilson and Lu, 1996]. Oneway is to support concurrency by providing a library, so users do not have to learnnew language features to be able to benefit from concurrency. Many such librariesexist for C++, for example ABC++ [O’Farrell et al., 1996], The Amelia Vector TemplateLibrary [Sheffler, 1996], and CHAOS++ [Chang et al., 1996].

A problem with such libraries is that since the underlying programming languagedoes not support concurrency, the syntax of the library can be awkward and non-intuitive. This is especially true if the paradigm behind the library does not resemblethe paradigm behind the language. It may also make learning to use the library quitedifficult, as knowing the underlying language does not necessarily help learning thelibrary.

A second option is to extend an existing programming language by adding newkeywords and constructs necessary for concurrent programming. MPC++ [Ishikawa etal., 1996], ICC++ [Chien and Dolby, 1996], and UC++ [Winder et al., 1996] are examplesof such extensions to C++.

From the programmers viewpoint language extensions are a satisfactory solution,as most of the language stays the same and the programmer only has to learn thenew constructs. The implementation is also often quite portable and efficient, as it ispossible to use a precompiler to map the new constructs to existing programming lan-guage constructs and calls to a run-time library providing support for concurrency.

2

CHAPTER 1. INTRODUCTION

However, extensions to language syntax cause problems with automatic style check-ers, code generators, and other tools which are written to accept a certain languagesyntax. Debugging programs may also become difficult if new constructs do not mapdirectly to existing language constructs.

1.3 Active objects

When concurrency is introduced into object-oriented programming, it is quite naturalto attempt to merge concurrency and objects — after all, object-oriented programmingis about thinking about everything in terms of objects. This leads to the concept ofactive objects. Active objects are objects whose methods are executed concurrentlywith the rest of the program. The idea of active objects is common enough to beclassified as a design pattern [Lavender and Schmidt, 1995].

Object level is not suitable for very low-level concurrency. For example, it is notreasonable to model individual machine-code instructions etc. as “objects” and try toexecute them in parallel. However, it is quite usable in higher-level concurrency. Ob-jects are encapsulated from the rest of the program, and this encapsulation makes itpossible to put the internals of an object in its own address space — another processorwith its own memory or even a remote machine. At the same time the encapsulationenforces the idea of strict interfaces which are the only way to use an object. Theseinterfaces provide a natural way of communicating between two concurrently run-ning parts of a program. It should be noted, however, that the active object approachis not the only one possible, but concurrency can of course be introduced as a mech-anism completely independent of objects. However, these approaches are outside thescope of this thesis.

If active objects are chosen as the way to introduce concurrency to object-orientedprogramming, there is still the decision of whether two or more methods of one act-ive object are allowed to execute concurrently with each other. There are benefits inboth possibilities. Allowing several concurrent methods is more flexible, but it alsoinvolves difficult mutual exclusion and synchronisation problems. If the number ofsimultaneously running methods is restricted to one for each active object, there areno internal mutual exclusion problems, as only one method may access the internalsof an active object at any time. These kinds of active objects resemble monitors, a pro-gramming construct to handle mutual exclusion in concurrent programming, madewidely known by Hoare [Hoare, 1974]. The KC++ system described in this thesis takesthe latter choice and allows only one running method per active object.

Even with just one method at a time, there is still one choice to be made: whetherto allow a method to give way to another method and continue afterwards. This be-haviour corresponds to the “signal” and “wait” operations in monitors. On the otherhand this “voluntary interruption” is sometimes quite handy, but on the other handit requires actives objects to remember and store the state of the method execution,execute another method and then use the stored state to continue the execution of

3

CHAPTER 1. INTRODUCTION

the original method. This can be implemented using multiple threads in each activeobject, but it increases the overhead of a method call. The current KC++ system fol-lows the path of its predecessor, UC++ (see Section 1.5), and does not allow methodsto be interrupted. This makes active objects fast and easy to implement, but also re-stricts their use (Chapter 8 lists future plans for KC++, including support for methodinterruption).

1.4 Motivation behind KC++

The KC++ system tries to overcome the problems mentioned in Section 1.2 by provid-ing new concurrent semantics to some existing C++constructs. The system consists ofa KC++-to-C++ compiler and a class library which provides run-time support for con-current processes and their communication. KC++ programs are syntactically validC++ programs (and vice versa), so existing C++ style analysers, statistical tools etc. canbe used with KC++ programs without modifications. In this respect KC++ resemblesC++// [Caromel et al., 1996], which also introduces concurrency without extendingthe C++ syntax.

KC++ is based on the UC++ language [Winder et al., 1996] and many of its designgoals are similar to UC++. It tries to add concurrency support to C++ as “naturally” aspossible, allowing existing object oriented language features and C++ programmingidioms to be used, without forcing the programmer to use of any particular codingand design style.

Like in UC++, concurrency in KC++ is based on the active object concept. A concur-rent KC++program usually consists of several active objects communicating with eachother. The active object model is also behind many other concurrent C++ systems, forexample C++// and ABC++.

Exceptions are now becoming more widely used in C++, and exception safety1 israpidly becoming more and more important in C++ programming (see [Cargill, 1994],[Reeves, 1996], [Sutter, 1997], and [Sutter, 2000], for example). KC++ tries to supportprogramming idioms needed in exception-safe C++ programming [Austern, 1998]. Forexample, KC++ active objects can be created as local variables, allowing programmersto rely on C++ stack unwinding to destroy unnecessary objects when an exception isthrown.

The code produced by the KC++ compiler is close to the original KC++ code. Mostmodifications are only changes to type names. This means that the code produced bythe compiler is expected to be debuggable with most normal C++ debuggers.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1The term exception safe is usually used to describe a part of a program which continues to work

properly no matter what exceptions occur.

4

CHAPTER 1. INTRODUCTION

1.5 The history of KC++

The history of the KC++ system began in 1998 during my half-year stay in the King’sCollege London under the supervision of Prof. Russel Winder. He had earlier beenworking on UC++, another C++-based concurrent programming system. The UC++ sys-tem is also based on the idea of active objects with one-method-run-to-completion se-mantics. When I arrived to King’s College looking for a research subject, Prof. Windercame up with the idea to start from the ideas behind UC++ and design a new sys-tem which would fit “more nicely” into the C++ language (Chapter 7 mentions someproblematic features in UC++).

During my stay I studied some existing concurrent programming systems basedon the C++ language, many of which are presented in “Parallel Programming UsingC++” [Wilson and Lu, 1996]. I noticed that most of these systems limited the use ofC++ or the way the in which concurrency features could be combined with C++. Ithen started to design KC++, trying to make it “comfortable” for a C++ programmerand as compatible with the existing C++ language semantics as possible. Frequentdiscussions with Prof. Winder helped me to refine some ideas and produced evenmore ideas for the KC++ system.

After the KC++ system started to take shape, Prof. Winder acquired a licence to usea C++ compiler front end from the Edison Design Group (EDG) [EDG, 1999]. Duringthe latter part of my stay I wrote a test implementation of the KC++ compiler and theKC++ library. The fact that KC++ doesn’t extend the C++ syntax helped me enormously,because I only had to write a back end to the EDG front end.

After I returned to Finland I did not work on KC++ for some time, but returned toit in the spring 1999, when I wrote an article on KC++ (still unpublished), which isthe basis of Chapter 2. I then made some minor improvements to the compiler andstarted to write this thesis (which was interrupted by another writing project, andtook a year to get finished).

1.6 The structure of this thesis

This thesis describes the KC++ system — a concurrent programming environmentbased on the C++ language and active objects. It contains both the description of theKC++ language and the basics of the implementation of the KC++ compiler and run-time library. The next chapters contain the following:

• Chapter 2 gives an overview of the KC++ system without going into too muchdetail. Its purpose is to give the reader a general idea of the basic structure ofKC++. These ideas are then covered in detail in the following chapters.

• Chapter 3 discusses the active objects themselves and compiler-generated proxyclasses which the active objects use to communicate with each other.

5

CHAPTER 1. INTRODUCTION

• Chapter 4 explains how different types of parameters are passed between act-ive objects. This subject is a chapter of its own because active objects do notnecessarily have a shared address space, so the standard C++ parameter passingmechanisms cannot be used.

• Chapter 5 shows how asynchronous method calls are implemented in KC++ us-ing future types. It also explains explicit synchronisation with future sources.

• Chapter 6 contains an overview of the KC++ compiler, which is used to com-pile KC++ programs into concurrent C++ programs. It also explains some mainfeatures of the KC++ library, which contains support code needed by KC++.

• Chapter 7 mentions some related concurrent programming systems and brieflycompares them to KC++.

• Finally, Chapter 8 gives the concluding words and discusses some ideas for thefuture versions of KC++.

6

CHAPTER 2. OVERVIEW OF KC++

Chapter 2

Overview of KC++

This chapter gives an overview of the KC++ system. It deliberately does not explaineverything in detail, but it is meant to give a picture of the scope of KC++. Detailsabout the features described here are given in the following chapters.

2.1 The KC++ active object concept

The KC++active object model is in many ways similar to the UC++active object model.Each active object consists of a thread of execution and a data address space. The dataaddress space contains all the data members of the object, method parameters andany data or objects which have been dynamically created inside the object. Becausethe address space of each active object may be distinct, active objects may not referto any global variables or static data members (which are basically global variables).

As mentioned above, each active object in KC++ contains one thread of execution.This thread receives method requests and serves them one at a time. Other methodrequests are collected in a queue while one method is executing. The execution of amethod is non-interruptible (i.e. it follows RTC (run-to-completion) semantics), so ifthe execution of a method is paused by synchronisation, the whole active object is“blocked” until the method can resume its execution. Usually method requests areserved in a strict FIFO (First-In-First-Out) order, but KC++ provides also lock objects(Section 2.4, explained in detail in Section 5.6 in Chapter 5), which allow a user torequest an exclusive access to an active object.

2.1.1 Active classes

In KC++ the programmer marks objects as active by deriving their class (directly orindirectly through other base classes) from a base class Active, which is defined inthe KC++ library. All objects created from these active classes are automatically activeobjects. This is similar to the approach used in ABC++ and other “type based” active

7

CHAPTER 2. OVERVIEW OF KC++

object systems, but different from UC++ where the way the object is created defineswhether an object becomes active or not.

2.1.2 Creating active objects

Creation of active objects happens exactly in the same way as creating normal C++objects. They can be created dynamically with new, as local variables inside a function(or any other code block), as data members of an object (or active object) or even asvalue parameters to a function or a member function (although this is rarely useful).This is different from most other parallel C++ systems where active objects usuallyhave to be created dynamically with new or in some language specific way. Listing 2.1shows how active objects can be created in KC++.

The possibility to create active objects without new is essential for exception safety.Objects created with new are not automatically destroyed, so it is the responsibility of

1 class MyAClass : public Active2 {3 public:4 explicit MyAClass(int i);5 MyAClass(const MyAClass& r);6 ~MyAClass();7 int foo1(int i);8 MyAClass* foo2(MyAClass& a);9 MyAClass& operator =(const MyAClass& r);

10 private:11 // Private implementation details here12 };13 void func(MyAClass a) // A parameter active object14 {15 a.foo1(3);16 }17 int main()18 {19 MyAClass a1(1); // A local active object20 int i1 = a1.foo1(3); // Synchronous21 Future<int> i2 = a1.foo1(4); // Asynchronous22 MyAClass* a2p = new MyAClass(2); // A dynamic active object23 MyAClass* a3p = a1.foo2(*a2p);24 func(a1);25 }

LISTING 2.1: Creation of an active class and active objects

8

CHAPTER 2. OVERVIEW OF KC++

the programmer to make sure that every object (including active objects) is destroyedproperly even when exceptions occur. On the other hand, the C++ exception mech-anism takes care of the destruction of any object whose lifetime ends because of anexception. This also holds for KC++ active objects, so all active objects which go outof scope because of an exception are automatically destroyed.

2.1.3 Proxies and reference counting

Although active objects are used in KC++ like normal objects, the KC++ compiler hasto create special code for them in the resulting C++ code to create new threads ofexecution, possibly in a different address space. In the rest of this section, the termserver refers to the actual concurrently running active object process, and the termclient refers to the code which uses the active object.

KC++ uses the Proxy pattern [Gamma et al., 1996, p. 207] to allow the client codeto use an active object as if it existed in the client’s own address space. This sametechnique is used in different forms in many other parallel C++ systems, for examplein UC++ and C++//. It is also commonly used in CORBA [OMG, 1998a, Ch. 20].

For each active class, the KC++ compiler creates an internal proxy class and re-places every occurrence of the active class in the client code with this proxy class.The proxy class has the same public interface as the active class, so proxy objects(proxies) can be used in place of the original objects. When a member function of aproxy is called, it forwards the request to the actual active object. This is similar tothe “Remote Proxy” pattern in [Rohnert, 1996]. Listing 2.2 on the next page showsparts of the code generated from the code in Listing 2.1.

Many parallel C++ languages introduce proxy classes by deriving them from theactive classes. However, this means that either the proxies inherit all the data mem-bers of the original classes making the proxies heavy-weight, or proxies are con-structed in some way “in place” of the original objects, which results in a compiler-dependent and often non-portable implementation (and usually only works whenobjects are created with new).

KC++ solves these problems by duplicating the public interface of the active classin the proxy class instead of using inheritance. This way the proxies can be madevery light-weight. Avoiding inheritance also allows the KC++ proxy methods to returnslightly different return values (futures) to take care of asynchronous return valuepassing (Section 2.2, explained in detail in Chapter 5).

To keep track of proxies, KC++ implements reference counting in active objects.Each active objects contains a counter which tells how many other active objects con-tain a proxy referring to the object. Additionally, inside each active object all proxiesreferring to the same active object share a proxy implementation data structure whichhas its own local reference count. Figure 2.1 on page 11 shows both reference coun-ters.

9

CHAPTER 2. OVERVIEW OF KC++

1 // In reality proxy classed are defined in separate headers2 class Proxy MyAClass : public Proxy3 {4 public:5 explicit Proxy MyAClass(int);6 // Base class destructor takes care of proxy destruction7 MyAClass(const Ref< Proxy MyAClass > r);8 Future< int > foo1(int);9 Ptr< Proxy MyAClass > foo2(Ref< Proxy MyAClass >);

10 Ref< Proxy MyAClass > operator =(const Ref< Proxy MyAClass > r);11 // Special interface for proxy implementation omitted12 };13 void func(Proxy MyAClass a) // A parameter active object14 {15 a.foo1(3);16 }17 int main()18 {19 Proxy MyAClass a1(1); // A local active object20 int i1 = a1.foo1(3); // Synchronous21 Future<int> i2 = a1.foo1(4); // Asynchronous22 Ptr< Proxy MyAClass > a2p = new Proxy MyAClass(2); // . . .23 Ptr< Proxy MyAClass > a3p = a1.foo2(*a2p);24 func(a1);25 }

LISTING 2.2: Part of code generated from the code in Listing 2.1

The two level reference counting makes proxies very light-weight (in essence theyare just smart pointers to the proxy implementation data structure). Copying and des-troying proxies locally only requires updating the local reference count. Only when aproxy is sent from one active object to another, the reference count in the actual act-ive object has to be updated. This helps to minimise message passing between activeobjects. The reference counters are also used to decide when an active object is nolonger needed and can be safely destroyed.

It would be impossible to update the reference counts using normal C++ pointersand references. KC++ implements its own proxy pointer and proxy reference templateclasses. These classes behave exactly as their built-in counterparts, but take care ofupdating the reference counts. The compiler replaces all pointers and references toactive objects with these templates, as can be seen from the code in Listing 2.2.

10

CHAPTER 2. OVERVIEW OF KC++

Refcount

2

Active ObjectImplementationImplementation

Proxy

Refcount 1

Proxy

ImplementationProxy

Refcount 2

ProxyProxy

FIGURE 2.1: The KC++ proxy reference counting model

2.2 Futures

Every active object method call is implicitly asynchronous in KC++. Methods return-ing void do not require any special treatment as there is no return value to return tothe caller. For any other return value type, future types are used to allow the actualreturn value to be returned later when the method execution completes. Futures havealso other uses (see Section 2.3). Chapter 5 discusses futures in detail.

The KC++ futures are very similar to futures in Multilisp [Halstead, 1985] andABC++, and wait-by-necessities in C++//. They also resemble IOUs in the Threads.h++library [Thompson, 1998]. A future type represents another type and is created froma template class parametrised with this type (the “underlying” type). Futures can be“bound” to values which will become available later in the computation. When thevalue of a future is requested, the future pauses the execution of the requesting threaduntil the value becomes known, thus forcing synchronisation. A future which doesn’tyet know its value is called a pending future.

2.2.1 Normal futures

The KC++ futures can be assigned to each other and copied (and thus passed as para-meters) without having to wait for the value. Futures can also be passed as parametersto other active objects (see Sect. 2.3). This is also possible without synchronisation ofpending futures.

11

CHAPTER 2. OVERVIEW OF KC++

Every future has an implicit type conversion to its underlying type. This con-version function first waits for the value to become available and then returns thevalue. Futures also have a type conversion in the opposite direction to create a futurefrom the value it represents. This conversion creates a future whose value is alreadyknown. This way futures can be used to replace normal variables without having tomake any other changes to the code.

In addition to type conversion operators, futures have a member function value()to get future’s value explicitly (of course an explicit type conversion can also be used).They also contain a member function wait() which waits for the value to becomeavailable but doesn’t return it. Similarly method isready() can be used to ask a futurewhether a value is already available.

When the KC++ compiler generates proxy classes from active classes, it replacesall method return types except void with futures of the same type, as can be seenfrom the code in Listing 2.2 on page 10. However, if a return type is already a future,it is not changed in any way (this enables explicit use of futures in the active classes).With these changes every method call through a proxy can be made asynchronous.When a proxy method is called, the proxy sends a method request to the active object,creates a future which is bound to the eventual return value of the method, and thenreturns the future without waiting for the method to be completed. From the user’spoint of view, whether the method call appears to be synchronous or asynchronousdepends on how the return value is handled.

To have a synchronous method call, the user can assign or copy the return valueof a method to a non-future variable. This causes the implicit type conversion to beused to get the actual return value from the future, and this blocks the process untilthe value becomes known. This happens on line 20 in Listing 2.1 on page 8.

If the return value of a method is assigned or copied to another future, no block-ing is needed and the method call is asynchronous. This is shown on line 21 inListing 2.1. By using futures everywhere where the actual value of the return value isnot needed, the user can delay synchronisation until it is absolutely necessary.

2.2.2 void-futures

There is a special type of future called a void-future (Future<void>) where the typethe future represents is void. These can be used to achieve pure synchronisationwithout passing any values between the processes. The void-futures do not havean implicit type conversion or the value() method, but they do have wait() andisready() methods for synchronisation. Section 5.4 in Chapter 5 covers void-futuresin detail.

The KC++does not use void-futures internally, but the programmer can use them toexplicitly synchronise with member functions which would normally return nothing.They can also be stored or passed from an active object to another as synchronisationtokens.

12

CHAPTER 2. OVERVIEW OF KC++

2.2.3 Future sources

In addition to normal futures, KC++ has types called future sources. Future sources arealso implemented as a template class, and they act as an explicit way to create normalfutures (the implicit way being the return value of an active object method call). Ina sense, normal futures are the “destination” of a value, and future sources are the“source.”

When a future source is created, it represents a place-holder into which a valuecan later be assigned. Normal futures can be created from a future source with a typeconversion. Initially, every normal future created from a future source is a pendingfuture without a value. Later, when a value is assigned to the future source usingits bind() method, all the futures created from the future source (and all the futurescreated from these futures) receive the given value.

Future sources are useful when an active object wants to return a value which isnot yet known when the method ends. They can also be used for explicit synchron-isation. Future sources of type void are especially suitable for synchronisation as theydon’t carry any actual value, but act as pure synchronisation objects. Section 5.5 inChapter 5 goes deeper into future sources.

2.3 Passing parameters to active objects

KC++active objects do not have to exist in the same address space. This means that thenormal C++ parameter passing mechanism cannot be used to pass method parametersto active objects, because normal C++ parameter passing requires a shared addressspace.

Active objects in separate address spaces communicate with each other using theKC++ message passing subsystem. All member function parameters have to be mar-shalled into a data stream which is passed as a part of the method request. A methodrequest message consists of the method identifier (identifying the requested method)and a data stream which contains all the marshalled parameters. When an active ob-ject receives the method request, it first identifies the method (using the identifier)and then unmarshals the data stream, creating local copies of the parameters.

The KC++ marshalling/unmarshalling system resembles the C++// marshalling sys-tem or the CORBA GIOP protocol [OMG, 1998a, Ch. 13]. However, KC++ does not storeany type information (like C++// meta-classes) during marshalling, so a marshalledparameter list contains just the marshalled parameter values concatenated one afteranother. Storing type information is not needed in a language like C++ because identi-fying the method already fixes the number and types of the parameters. Each differentversion of an overloaded method is considered a different method with a differentmethod ID, so the method ID already contains all the information about parametertypes. Parameter passing is the subject of Chapter 4.

13

CHAPTER 2. OVERVIEW OF KC++

2.3.1 Normal parameters

The KC++ library contains functions to marshal and unmarshal all C++ built-in typesexcept for pointers and references. This makes passing parameters of built-in typesto active objects identical to passing them as parameters to normal objects.

If the user wants to pass his own data types (data structures, non-active objectsetc.) to an active object, he must write appropriate marshalling and unmarshallingfunctions for them. The KC++ library provides the classes OMsg (output message) andIMsg (input message), which represents data streams to and from which data is to bemarshalled to using an iostream-like operator <<. The data is unmarshalled from thestream using either an unmarshalling constructor or an operator >>. These can beoverloaded for user-defined data types.

The only exceptions to this are pointers and references which cannot be passed asparameters at all, because they would require shared memory (and even with sharedmemory would pose mutual exclusion problems). However, pointers and referencesto active objects and references to futures are allowed. These will be explained below.

2.3.2 Future parameters

KC++ future types are allowed as parameters to active object methods. The only re-quirement is that the underlying type of the future type must also be valid as a para-meter. The semantics of passing a future as a parameter depends on whether thefuture already contains a value or whether it is still pending.

If the future has already received its value, passing it as a parameter is identical topassing its value. As there is a type conversion from the underlying type to the futuretype, it is also possible to pass instances of the underlying type as parameters tomethods which expect future types. Doing this is identical to passing a future whichcontains the given value.

If a pending future is passed as a parameter to an active object, a copy of thepending future is created at the active object side and this future is bound to thefuture on the caller’s side. This way, the value of the future can remain unknowneven when it is passed as a parameter. Later, when the value of the future becomesknown, the KC++ run-time system takes care of forwarding the value to other activeobjects which contain copies of the future.

2.3.3 Future reference parameters

In addition to return values, reference parameters are a standard C++ way to passinformation back from a method. This is not directly possible with active objects be-cause normal reference (and pointer) parameters cannot be allowed. The same effectcan be achieved by having a future reference as an active object member functionparameter.

14

CHAPTER 2. OVERVIEW OF KC++

The semantics of future reference parameters is close to the in-out parameters insome languages. When a future is passed as a reference parameter to an active object,the future is passed normally as a parameter. Then the future on the caller’s side losesits value and becomes pending again. It remains in this state until the execution ofthe method has completed in the active object. At this point the future receives thefinal value of the corresponding parameter on the active object side.

2.3.4 Active object parameters

Unlike with normal parameters, there are no restrictions on using active object point-ers or references as parameters to active object methods. Active objects already havea possibility to refer to each other through proxies. In a sense all active objects oc-cupy the same “active object address space,” so passing active object pointers andreferences between active objects poses no problems.

It should be noted that KC++ follows the normal C++ parameter passing semanticswhen passing active objects. If an active object is passed by value, a copy of the activeobject is created (using the active object’s copy constructor) and that new object isthen passed as the actual parameter. When passing pointers and references to activeobjects, no copies are made. This allows the programmer to use the same coding stylein passing both normal objects and active objects as parameters.

2.4 Locking active objects

Quite often a service provided by an active object cannot be completed within onemethod call, but requires several calls with some processing outside the active objectbetween the calls.

An example of this could be a read buffer handled by an active object. A user firstchecks whether the buffer is empty (first method call) and then retrieves a value if itwas not (second method call). It is important that another user is not able to “steal”the value (and empty the buffer) between the calls.

A normal solution to these problems are object locks. Normally an active objectserves method requests in FIFO order (which is often non-deterministic as the orderin which requests arrive may be non-predictable). However, when a user locks anactive object, that object serves only method requests from that user (in FIFO order)until the lock is released.

KC++ uses lock objects to provide exception safe object locking. When a user wantsto lock an active object, he surrounds the code which requires the lock with bracesand thus creates a new local scope. At the beginning of this code block he creates alock object of type ActiveLock using the active object as a constructor parameter.

The lock object makes sure that only the creator of the lock object can access theactive object for the lifetime of the lock object. When the lock object is destroyed at

15

CHAPTER 2. OVERVIEW OF KC++

the end of the scope, the lock is automatically released and others are again allowedto use the active object.

16

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

Chapter 3

Active objects and proxies

Active objects and their proxy object implementation form the basis of KC++. Thischapter describes in detail the KC++ active object model and compares it to otheractive object based concurrent object-oriented languages.

KC++ uses a class-based active object model. This means that the programmer can-not mark individual objects active, but he can do so for whole classes. After thisall objects instantiated from such a class are automatically and always active objects.Other C++-based languages to use a class-based model are ABC++ [O’Farrell et al., 1996]and C++//, for example. Some concurrent object-oriented systems like UC++ [Winderet al., 1996] use an object-based approach where the programmer can make an objectof any class active. In C++// both these approaches are possible.

The main reason behind choosing a class-based model in KC++ is that an activeobject is conceptually different from a normal one. All active object methods areexecuted asynchronously, and therefore an active object and an otherwise similarnormal object should not belong to the same class.

This difference between an active object and a “passive” object is also dictated bythe KC++ language implementation. All active object method calls are asynchronous,and methods not returning void return futures as return values. Returning futures isimplicit in KC++ (see Section 3.2), i.e. the programmer does not have to explicitly markmethod return types as futures (return types can be explicitly marked as futures, butthis has a slightly different semantics, see Section 5.2.3). This means that the actualreturn types of active object methods are different from otherwise identical non-activeobject methods, making it impossible to combine both in the same class.

3.1 Making objects active

The programmer declares a class active by deriving the class from a base class calledActive. After that every object of that class is automatically an active object with itsown thread of execution.

17

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

An active class does not have to be directly derived from the base class Active.It can also be derived from an existing active class, in which case there is a normalsubtyping relationship between the classes.

A third alternative is to create an active class from an existing non-active classby using multiple inheritance and deriving the active class both from the non-activeclass and the class Active. In this case there is no subtyping relationship betweenthe active class and its non-active base class in current KC++. The reasons for this arediscussed in Section 3.3. Figure 3.1 shows the possible inheritance hierarchies.

3.2 Proxy classes

When a class is marked active, the KC++ compiler creates a corresponding proxy classand replaces all instances of the active class with the proxy class. In the resultingprogram all communication with the active object happens through a proxy objectcreated from the proxy class.

The public interface of the proxy class is almost identical with that of the activeclass, with the exception of future return types described in Chapter 5. However, theinternal implementation of proxy objects is quite different. The proxy object does not

Active

A

(a) Direct

Active

ActiveClass

A

(b) Indirect

A

Active NonActiveClass

(c) From non-active

FIGURE 3.1: Different ways of making a class active in KC++

18

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

contain any of the data members of the original class, as those data members reside inthe active object itself. This somewhat restricts the structure and use of active classes.

The first restriction is that an active class may not contain any public data mem-bers because they are not present in the proxy class. It would be impossible to imitatedata members in the proxy object because an object has no control over the outsideuse of public data members. However, use of public data members is normally dis-couraged in object-oriented programming, so this restriction is not likely to causemany problems.

Even though public data members are not considered good programming practise,it is quite common for an object to access data members of another object of thesame class. Since active objects communicate with each others through proxy objectswhich lack these data members, this kind of access is also impossible. Section 3.4.2discusses ways to circumvent the limitation.

3.2.1 Internals of a proxy

The core of the a proxy object’s implementation is a data structure called the “proxyimplementation structure.” This structure contains enough data to uniquely identifythe active object which the proxy represents. This data is also used to send themethod requests and future values to the active object.

The actual data stored in the proxy implementation structure depends on thecommunications library used to send messages from one active object to another.In a distributed environment it might consist of a TCP/IP address and the messageport the active object’s method invoker listens to. In a shared memory environmentit would be enough to store a pointer to a active object’s method queue and a mutualexclusion semaphore. The important thing is that the data in the structure containsenough information for contacting the active object and deciding whether two proxyobjects refer to the same active object. In this thesis, the active objects are simplyidentified by a single integer for simplicity.

Active object references and pointers to active objects also use the proxy imple-mentation structure. To make creation of such references and pointers as fast as pos-sible, the proxy implementation structure is not stored as a data member inside theproxy object, but the proxy object contains a pointer to the structure. This allowsthe proxy, references, and pointers to share a proxy implementation. They also main-tain a reference count so that the proxy implementation structure is automaticallydestroyed when it is no longer needed. Active object references and pointers are de-scribed in detail in Section 4.3.

When a new active object is created, initially there is only one proxy object. Whenreferences and pointers to this active object are created, additional proxies are made.The reference count in the proxy implementation structure allows local copies of theproxies to share the structure. However, when a reference or a pointer to a proxy ispassed to another active object, a copy of the proxy must be created in the address

19

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

space of the receiving active object. In the current version of KC++ an active objectitself contains a reference count of other active objects to contain proxies to the activeobject. This reference count is used to determine when the active object can be safelydestroyed. The proxies send a message to the active object each time the referencecount must be incremented (i.e. when the proxy is about to be copied across activeobjects). Similarly, when the destructor of a proxy notices that it is the last to use acertain proxy implementation structure, it sends a request to the active object askingit to decrease its reference count.

3.2.2 Lifetime of an active object

When code in the program creates a new proxy object, it means that a new activeobject must be created in the system. The constructor of the proxy takes care of this.It first signals the creation of a new active object process. Depending on the envir-onment the KC++ is implemented on, this might simply mean the creation of anotherexecution thread. On the other hand, on a distributed environment it might requireconsulting some central load-balancing system on the location of the new active ob-ject. After the active object process has been created, the constructor of the proxysends it a message which requests it to execute the appropriate constructor.

After the proxy object has been created, the program can use the proxy — i.e. callits methods — just as if the proxy was the active object itself. The code in the methodsof the proxy relay method requests to the actual active object. This will be describedin Section 3.4. The only difference between the public interface of the proxy class andthe public interface of the active class is that proxy methods return futures, whichallows asynchronous method execution.

In principle the destructor of the proxy could also trigger the destruction of theactive object (which includes running the actual destructor of the active class). How-ever, active objects created dynamically with new can be created in one active objectand then destroyed with delete using a different proxy in another active object. Thismeans that the destruction of a proxy cannot automatically destroy the active object.Passing active object references to other active objects and asynchronous method ex-ecution create also some problems, which are discussed in Section 4.3.1.

Because of these problems the current version of KC++ only destroys an active ob-ject after all proxies referring to it have been destroyed. This includes proxies impli-citly created by active object references and pointers. This behaviour solves the earlydestruction problems, but it differs slightly from the normal C++ behaviour and cre-ates a possibility for resource leaks. For these reasons the future versions of KC++ mayuse a different mechanism for deciding when to destroy active objects (see Chapter 8).

20

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

3.2.3 Proxies and inheritance

When active classes are derived from each other, the proxy classes created by the KC++compiler form an identical inheritance hierarchy. This kind of duplicated inheritancehierarchy is known as the “Rungs of a Dual Hierarchy” pattern [Martin, 1997] andcan be seen in Figure 3.2. The proxy hierarchy becomes important when the KC++compiler replaces all active classes with proxy classes. If the code contains placeswhere a base class reference or pointer is tied to a derived class active object, thiscode continues to work in the produced code where a proxy base class reference orpointer is tied to a derived class proxy object.

The KC++ class Proxy forms the top of the proxy hierarchy. This class containseverything a proxy object needs to communicate with its active object, i.e. the pointerto the proxy implementation structure and member functions to create and send mes-sages to the active object. Because all these functions are implemented in a commonbase class, the program contains only a single copy of their code, no matter how manyproxy classes are generated. This helps to keep the size of the executables as small aspossible.

The actual proxy classes only add necessary member functions to the public in-terface so that each proxy object can imitate the appropriate active object. The imple-mentation in each of these methods simply creates an appropriate method message,marshals the parameters into the message and then sends the message to the activeobject. Most of this work is done by calling methods of the base class Proxy, so thesize of these methods is quite small. No additional data members are needed in theproxy classes, as the proxy implementation structure in the base class is enough toaccess all active objects regardless of their class.

Because of the inheritance hierarchy each method has to be implemented onlyonce, in the first base class where it appears. From there it is automatically inherited

Proxy

Proxy_A Proxy_B

Proxy_C Proxy_D

Active

A B

C D

FIGURE 3.2: Inheritance hierarchies of active classes and proxies

21

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

to all derived classes. Dynamic binding is implemented on the active object side, soit is not needed in proxies objects. This is essential because active object pointersand references can cause situations where a base class proxy (for example Proxy_Bin Figure 3.2) is used to access a derived class active object (for example D). Whena method of the base class proxy object is called, the base class method is alwaysexecuted because it is the only one available. However, when the message sent bythis proxy method is received in the actual active object, dynamic binding is usedto find the appropriate method in the active object. This way base class proxies canrefer to derived class active objects without problems. This situation is discussed indetail in Sections 3.5.3 and 4.3.1.

3.3 Inheritance and active classes

The class hierarchy of active classes behaves exactly as normal C++ class hierarchies,so pointers and references to active base classes can point to objects of derived activeclasses. Similarly dynamic binding works as expected with active objects. The onlyrestriction with the current implementation of KC++ is that support for RTTI (Run-Time Type Identification) is not yet implemented.

In KC++ an active class may also have non-active base classes through multipleinheritance. From the viewpoint of the active class’s own code this inheritance worksas intended, i.e. a method of the active class can call public and protected methodsof a non-active base class. This mechanism allows “reuse” of existing normal C++classes. Appendix A shows an example of this by defining an active buffer class basedon an existing buffer.

Using active classes based on non-active base classes is a little bit more restrictivein the KC++. The public methods of the non-active base class are accessible throughthe active class. Futures are used normally in the return types of these methods toprovide asynchronous method calls. However, non-active base class pointers (or ref-erences) cannot point to a derived active object. So, outside the implementation ofthe methods of the active class, there is no subtype relationship between the classand its non-active base class.

The reason for this restriction is the way KC++ active objects are implementedby replacing the actual active objects with light-weight proxies and instantiating theactual active object elsewhere. If the proxy classes would be derived from non-activebase classes, they would also inherit the implementation (i.e. data members) of thebase class. This would be redundant (as the proxy just acts as a relay between theuser and the actual active object), and it would also make the proxy possibly veryheavy-weight.

Non-virtual methods of the base class would also be problematic. There is noperfect way to override the implementation of these methods in a derived proxy class,as dynamic binding is only used with virtual methods. However, the proxy class

22

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

needs to override all methods of the base class because it has to forward all methodscalls to the active object.

Another reason not to derive proxy classes from non-active base classes is the factthat every method call to an active object is asynchronous. This would be impossibleto achieve for base class methods which return something else than void (or an expli-cit future), because the base class call would be synchronous (a normal C++ methodcall) but the derived proxy class call should be asynchronous. This would also causeproblems in the type system. Because every non-void (and non-future) return valueis replaced with an appropriate future in the proxy class, the interfaces of the proxyclass and the non-active base class would no longer be compatible in the C++ inher-itance sense (although to the user they are practically identical as the future returnvalue emulates a normal return value).

3.4 Calling active object methods

Figure 3.3 shows how an active object method call is performed using a proxy ob-ject. The method call proceeds through the proxy object in the following way (returnvalues are covered in Section 3.6, here void return type is assumed for simplicity):

1. When program code calls a method of the active object, the KC++compiler trans-forms this to a method call of an appropriate local proxy object.

Activeobject

objectProxy

A

A.Method1();

invokerMethod

Method queue

Active object process

1.

2. 3.

...

...

"User process"

Method1

Method2

Method3

Method2

Method1

Method3

FIGURE 3.3: An active object method call

23

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

2. The method in the proxy creates a message specifying the caller and the method.Then all parameter values are marshalled to the message as well (marshallingis covered in Section 4.1). The message is then sent to the active object.

3. In the active object the message enters a message queue where it waits for itsturn. When the message is at the head of the queue (or all messages before it arelocked out, see Section 5.6 in Chapter 5), the method invoker reads the messageand unmarshals all parameter values into local variables (unmarshalling is alsocovered in Section 4.1). After this the method invoker calls the method with theunmarshalled parameters using a normal C++ member function call.

When the active object method call completes, and if the method had a non-voidreturn type or the parameters contain future references, the method invoker may senda message containing values to be passed back to the caller. This message is part ofthe KC++ future binding mechanism and is covered later in Chapter 5. Finally all localparameter values are destroyed and the method execution is complete.

3.4.1 Local method calls

The active object method invocation mechanism described in the previous section isused only when methods of an active object are called from outside. When an activeobject method calls another method of the same object, the situation is somewhatdifferent. Below these method calls from another method of the same active objectare called “local method calls.”

All non-local method calls enter the method queue and are executed in strict FIFOorder (with the exception of object locking). This combined with the one-method-run-to-completion semantics of KC++would cause problems with local method calls. Mostlocal method calls are inherently synchronous in nature, i.e. even if the method callwould be asynchronous, the calling method would still have to wait for the returnvalue.

If local method calls would use the normal method queue, this would lead to adeadlock as the calling method would still be executing and waiting for the localmethod to complete, but the local method would only be taken from the queue andexecuted after the first method had been completed.

The KC++ system solves this problem by treating local method calls differentlyfrom non-local calls. All local method calls are considered privileged and are ex-ecuted immediately without waiting for the calling method to complete, thus loc-ally ignoring the run-to-completion semantics. This follows the view that most localmethod calls are calls to private auxiliary functions whose execution should be con-sidered to be part of the execution of the calling method. It is somewhat analogous toEiffel, where class invariants are not required to hold (or at least they are not checked)for local method calls [Meyer, 1992].

24

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

One technical difference between a local and a non-local method call is that theproxy invocation method is not needed in the local case because the active objectand the calling code are known to be in the same address space, and so the activeobject is directly accessible to the caller. KC++ utilises this situation by leaving alllocal method calls untouched, as normal C++ member function calls. This removesthe overhead of normal active object method invocation as the intermediate proxycall and the method invoker are not needed. At the same this automatically makeslocal calls privileged as they never even enter the method queue. The solution alsoallows the actual C++ compiler to inline and otherwise optimise local calls normally,which can further speed up the execution of local methods.

If the programmer wants a local method call to be executed as a non-privilegedmethod call, the method can be called indirectly using this−>method(). These callsare converted to normal proxy method calls which enter the method queue and areinvoked in normal FIFO order. It should be noted that the calling method must notstart waiting for completion of the called method, because the called method startsexecuting only after the calling method has been completed.

3.4.2 Accessing private members of other objects

Another implication of leaving local calls as normal C++ calls is that the proxy classesdo not have to contain private member functions of the active object, as these arenot accessed through the proxy. This reduces the size of code generated by the KC++compiler as proxies become smaller.

However, C++ allows objects of a class to access private members of other objectsof the same class. The most common use of this is to access data members of otherobjects of the same class. With active objects this is impossible, however, because thedata members of another active object are possibly in a different address space. Forthis reason KC++ does not allow any kind of access to the private members of otheractive objects.

Sometimes it would be convenient to have a special privileged interface betweenobjects of the same class. In C++programmers usually use the private access describedabove and write special private member functions which are accessed from other ob-jects of the same class. This kind of use of private members is convenient but unfor-tunately the KC++ compiler cannot find out which private member functions are usedonly locally and which are accessed from other objects. This happens because thecompilation is performed separately for each translation unit, so there is no “globalpicture” of where member functions are called from.

Current KC++ offers an inelegant solution to this problem by allowing active ob-jects of one class to call protected member functions of other active objects of thesame class. This solution is a compromise as it conflicts with the usual meaning ofthe keyword protected. However, some distinguishing feature was needed between

25

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

local-only and non-local private methods, so the unusual use of keyword protectedwas considered an acceptable sacrifice.

3.5 Choosing the correct method

Method messages sent from a proxy object to its active object must identify themethod to be called and all the parameters (the message must also identify the re-turn value future, but this is explained later in Section 5.2). The message must con-tain enough information for the active object to uniquely identify the correct memberfunction and retrieve the parameter values from the message.

Many concurrent object-oriented systems implement this using some sort of re-stricted metaobject protocol [Kiczales et al., 1991]. In these protocols the methodmessage represents a “method invocation metaobject” — an object which containsdata about the name of the method and the values and types of all its parameters.When the method message is received in the active object method invoker, the para-meter type information in the message is used to retrieve the parameter values, andthe method name is used to find the correct member function. After this the memberfunction can be called normally. It should be noted that metaobject protocols havemany other advantages in addition to method invocation. Especially they make cus-tomizing and extending the system easier.

The metaobject protocol approach to method invocation is elegant, but increasesthe overhead needed in parsing the method message. However, a metaobject protocolis not necessarily needed for method invocation in a language like C++, which isfairly strongly typed and uses pass-by-value as its parameter passing mechanism. Analternative is to use a method ID generation scheme which is similar to the namemangling scheme used in most C++ compilers [Lippman, 1996, p. 117].

In this approach the ID of every method is uniquely generated from the methodname and the types of the formal parameters (the parameter types are needed becauseof function name overloading in C++). The method name and formal parameter typesdo not change, so all method IDs can be generated at compile-time. The method mes-sage can then simply consist of the method ID and a bit-stream containing the valuesof all parameters in the method invocation. No parameter type information is neces-sary as it is compiled into the method ID. Figure 3.4 on the next page shows how amethod message can be constructed from the member function declaration and theactual method call.

In many object-oriented languages the scheme described above would break whenpolymorphic objects are passed as method parameters. In these cases it is possiblethat the actual type of the parameter differs from the formal type of the parameter.For example, an object of type D derived from class B could be passed as a para-meter to a method expecting an object of type B. If parameters were by default passedby reference (as is the case in many object-oriented languages, but not in C++), the

26

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

Activeobject

objectProxy

A

A.Method1(3, 2.0);

invokerMethod

class A : public Active{ ... void Method1(int i, float f);};

Method message

Active object process

...

"User process"

...

Method1

Method2

Method3

Method2

Method1

Method3

3 2.0Method1:int:float

3 2.0

FIGURE 3.4: Creating the method message

method ID would identify the parameter as type B even though the marshalled objectin the method message would be of type D. This would probably break the messageparsing mechanism as the bit-stream containing the parameter would be interpretedincorrectly.

This problem does not apply to KC++ which uses the same default pass-by-valueparameter passing mechanism as C++. The reason for this is best described by divid-ing the possible formal parameter types to categories and discussing them one at atime.

3.5.1 Basic types

Basic C++ types like int, char, double etc. pose no problems as these types are notpolymorphic, and therefore the actual parameter type is always the same as the formalparameter type. The implicit type conversions are not a problem either because theyare carried out before the method call.

27

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

3.5.2 Class types and structs

Class types combined with subtyping and inheritance are usually the reason why ametaobject protocol is needed. In languages based on C++ this also applies to structtypes because they are semantically similar to classes.

When an object is passed as a parameter, it is possible that the type of the actualparameter is in fact derived from the type of the formal parameter. In this case thepass-by-value semantics of C++ eliminate the problem. Even though the type of theparameter differs from the type of the formal parameter, the actual value passed tothe method is a sliced1 copy of the actual parameter. This happens because pass-by-value implies creating a copy of the parameter, and in C++ the type of the copy isdetermined statically, ignoring the dynamic type of the parameter.

Slicing is a common source of problems in C++, but in this case it also means thatpolymorphism is not an issue when objects and structs are passed as value paramet-ers.

3.5.3 Pointer and reference types

Pointers and references are also capable of expressing polymorphic behaviour andare therefore capable of creating problems with parameter passing. In KC++ pointersand references can be divided into two categories, which are discussed below in theirown subsections.

Pointers and references to non-active types

Pointers to basic types, non-active classes or structs cannot be passed as parametersto active object methods, because the objects they point to are not necessarily in anaddress space which is accessible from the active object. For this reason these pointersand reference do not cause any problems.

Pointers and references to active objects

Pointers and references to active objects are allowed as active object method para-meters in KC++, because active objects can be thought to occupy a “global” addressspace consisting of all active objects in the program. When an active object pointeror reference is passed as a parameter, only the object ID is stored in the message.The format of the ID is the same regardless of the type of the active object. When themethod message is received, the method invoker creates a local, possibly sliced proxy

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1Slicing is a term used to denote the operation of creating a base class copy of an object of a derived

class. This is possible because the object of the derived class is also an object of the base class andthus is acceptable as a parameter for the base class copy constructor. The implications of slicing arediscussed in [Budd, 1997, p. 221], for example

28

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

object in the address space of the receiving active object. Slicing of the proxy objectdoes not cause problems, however, because dynamic binding is implemented on theserver side, so a correct method is called even if the method is called through a slicedproxy object.

Figure 3.5 shows a method call where a pointer to an active object of a derivedclass B is passed as a parameter to a method expecting a pointer to base class Base.The receiving active object creates a proxy which is of the generalised type, but thatproxy can still be used to call all base class methods, which are the only methodsvisible through a base class pointer.

a.Method1(b);

objectProxy

A

ID=1

}; void Method1(Base* p); ...{ : public Activeclass A

class Base : public Active...class B : public Base...

A

Activeobject

Proxyobject

ID=2

Base

B

Activeobject

Proxyobject

B

ID=2

Method message

...

"User process"

...

Active object ID=1

Active object ID=2

a

p

b1.

2.

3.

Method2

Method1

Method3

Method1

Method2

Method3

Method1

Method2

Method3

Method1:Base* 2

FIGURE 3.5: Passing a polymorphic object

29

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

3.6 Returning values from active objects

All active object method calls are asynchronous in KC++. This is easy for methodsreturning void, as there is no need to synchronise with the method.2 Asynchronousmethods with a return value are more complicated, because the return value is notavailable immediately after the method has been called. Many concurrent object ori-ented systems like CORBA [OMG, 1998a] and UC++ circumvent this problem by onlyallowing methods with no return value to be called asynchronously (the new CORBAmessaging specification [OMG, 1998b] provides also an asynchronous method invoc-ation (AMI). An overview of AMI can be found in [Schmidt and Vinoski, 1998]).

As mentioned in Chapter 2, KC++ active object methods use future types to returnvalues from methods. Futures are covered in detail in Chapter 5, but this sectiondiscusses briefly the way return values are returned from a method.

For methods returning a value, asynchronous method calls cannot be implemen-ted without some modifications. If an asynchronous method returning an int wouldsimply return an int, the caller would have no way of knowing when the return valuebecomes available. An additional synchronisation layer is needed so that the callercan access the return value only after the method has completed.

Futures are such a layer, introduced by R. Halstead in Multilisp [Halstead, 1985].Futures are place-holders for values which will become available later. They are usedfor asynchronous return values in many concurrent programming languages and lib-raries, for example in ABC++ [O’Farrell et al., 1996] and C++//. The KC++ futures havealso other uses besides return values from asynchronous methods. These uses will becovered later in this section.

The KC++ futures are implemented as a template class. The type the future is usedto represent is given to the future as a template parameter. If the original return valuefrom a method is int, the corresponding proxy method (generated by the KC++ com-piler) has return type Future<int>.

Figure 3.3 on page 23 showed how an active object method call works, but omittedthe return value passing mechanism. That is shown in Figure 3.6 on the followingpage. The return value passing proceeds as follows:

1. The caller creates a future variable for the return value. Initially the future be-haves exactly as a normal variable (in this case int) and is initialised to theappropriate default value (0 with ints).

2. When the caller invokes a method in the local proxy object, the proxy codecreates a future object which does not contain a value (these kinds of futuresare called pending futures in KC++ and will be explained in Chapter 5). Theproxy then includes the ID of the future in the method message which is sentto the active object.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .2However, often synchronisation is wanted even when the method returns no actual value. In KC++

this is also possible with void-futures, see Section 5.4.

30

CHAPTER 3. ACTIVE OBJECTS AND PROXIES

Activeobject

objectProxy

A

invokerMethod

Method queueFuture<int> i; // 1i = A.Method1(); // 2,3... // 4,5if (i == 0) ... // 6

Active object process

2.

"User process"

i3.

4.

1. 5.6.

Method1

Method2

Method3

Method2

Method1

Method3

FIGURE 3.6: Returning a value from an active object method

3. The proxy returns a copy of the future created during the previous step, and thecaller assigns this future to a future variable. The lazy-evaluation semantics offutures allow this to happen even if the future is still pending. The result of thisis that the future variable i becomes pending and is bound to the eventual re-turn value of the method. All the intermediate future objects are destroyed auto-matically by C++. After this step all attempts to read the value of i are blockeduntil its value becomes available.

4. The method is executed in the active object as a normal C++ member functioncall. At the end of the method the return value is returned to the method invokerusing normal C++ return value passing. The return value from the active objectmay also be a future, i.e. it itself may represent a value that is not yet known.

5. The method invoker creates a “reply message” which identifies the future onthe caller’s side and contains the marshalled value for the future.

6. The KC++ library receives the message on the caller’s side and uses it to givethe future its value (the part of the KC++ library responsible for receiving futurevalue messages is described in Section 6.2.3). If the caller had tried to accessthe future’s value and had been blocked, the block is removed the caller cancontinue executing its code.

The message containing the return value of a method is labelled as a “reply mes-sage” above. In reality a special “reply message” type is not needed because the KC++future library can be used to send the return values between active objects.

31

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

Chapter 4

Passing parameters to active objects

By default C++ parameter passing uses pass-by-value semantics to create copies ofthe actual parameters inside the function. Pass-by-reference semantics is achieved byexplicitly passing a reference or a pointer. Pass-by-reference is normally implemen-ted by passing the memory address of the parameter object, which means that it isonly meaningful if both the calling code and the function are executed in the sameaddress space. Pass-by-value also requires a shared address space, because the actof creating a copy of the parameter requires access to both the address space of theactual parameter and the address space of the copy.

In KC++ active objects may occupy different address spaces, so parameter passingwith active objects methods cannot be implemented exactly the same way as nor-mal C++ parameter passing. However, the KC++ mechanism tries to imitate the C++mechanism as closely as possible.

4.1 Marshalling and unmarshalling

The KC++ parameter passing is based on marshalling and unmarshalling parametervalues to and from method messages. The proxy object on the calling side stores(marshals) all parameter values into the method message. In the receiving end themethod invoker reads (unmarshals) the values and creates temporary variables intowhich the values are stored for the duration of the method call. It should be notedthat if the caller and the active object reside in the same address space, marshallingand unmarshalling could be replaced with normal C++ object copying.

Marshalling happens using the C++ operator << and the KC++ class OMsg whichrepresents an outgoing method message. The marshalling syntax resembles the C++ostream output syntax. An object o can be marshalled into a message m with the ex-pression

m << o;

32

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

Marshalling operators can be chained together just like their ostream counterparts.

Unmarshalling is handled a little bit differently in KC++. Like the class OMsg formarshalling, KC++ has a class IMsg which represents a received method message. Un-marshalling an object from this message includes also creation of the object to be un-marshalled. This is achieved by implementing unmarshalling as a constructor whichtakes an IMsg stream as a parameter. An object o of class X can be unmarshalled froma message m with the expression

X o(m);

Unmarshalling of basic types must be handled a little bit differently as they can-not have user-defined constructors. For these types KC++ has the unmarshalling oper-ator >> with which a value can be read into an already existing basic type with theexpression

m >> o;

It is also possible to chain these unmarshalling operators together. The KC++ library in-cludes predefined marshalling and unmarshalling operators for all basic types whichare acceptable as active object method parameters.

4.2 Different types of parameters

The different ways to pass parameters to active object methods can be divided to thefollowing six categories.

4.2.1 Basic types

Basic types such as int, float, bool, etc. are passed as parameters just as with normalC++. Their values are automatically marshalled into the method message by the proxyobject and unmarshalled at the other end. This mechanism is indistinguishable fromnormal C++ pass-by-value (except that it is slower).

4.2.2 Non-active user-defined classes and structs

User-defined class and struct types require little extra work from the programmer asKC++ does not have built-in mechanisms for user types. In order to be able to pass anobject of a user type to active object methods, the programmer has to provide his ownmarshalling and unmarshalling mechanisms for the type.

For marshalling to work for type X, a marshalling operator must be defined. Thesignature of this operator is

OMsg& operator << (OMsg& omsg, const X& x);

33

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

The operator should return its first parameter as its return value so that chaining ispossible. For most struct and class types writing the operator is easy, as marshallingcan be implemented by marshalling all data members into the OMsg stream.

Unmarshalling requires a constructor which receives the IMsg stream to unmar-shal from as a parameter. The signature of this constructor for class X is

class X{public:X(IMsg& imsg);

};

The constructor is usually easy to implement as it can pass the IMsg stream to theconstructors of those data members that are of struct or class types. Data members ofbasic types can be unmarshalled in the constructor body using the >> operator. User-defined classes and structs can define the unmarshalling operator >> in addition tothe unmarshalling constructor, but the KC++ itself never uses this operator. However,user code may call this operator when implementing unmarshalling mechanisms forother classes and structs.

Listing 4.1 on the next page shows a simple class and its marshalling operator andunmarshalling constructor.

4.2.3 Enumeration types and non-user-defined types

Enumeration types, classes, and structs not defined by user — from third party lib-raries or the standard C++ library, for example — have to be handled a little bit differ-ently. Marshalling can be implemented normally with the << operator, but unmar-shalling requires a different approach because it is often not possible to add unmar-shalling constructors to third party code.

Unmarshalling for these kinds of types is handled by writing unmarshalling oper-ators (>>) for these types and declaring them as special cases with KC++ library macroSPECIAL UNMARSHAL. For type X, the syntax of these is

IMsg& operator >> (IMsg& imsg, X& x);SPECIAL UNMARSHAL(X)

The SPECIAL UNMARSHAL declaration must appear in the same header file where the>> operator is declared. In addition KC++ requires that these types have a defaultconstructor which can be called to create a “virgin” object into which the value can beunmarshalled with the >> operator. All enumeration types have this automatically,so this is an issue only with third party types. The need for SPECIAL UNMARSHAL isfurther discussed in Section 6.2.5.

Listing 4.2 on page 36 shows an enumeration type and its marshalling and un-marshalling operators.

34

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

1 class Simple2 {3 public:4 Simple(IMsg& imsg); // Needed for unmarshalling

...5 private:6 MyClass object;7 double length;8 bool flag;9

10 friend OMsg& operator << (OMsg& omsg, const Simple& s);11 };12

13 Simple::Simple(IMsg& imsg) : object(imsg), length(0.0), flag(false)14 {15 imsg >> length >> flag;16 }17

18 OMsg& operator << (OMsg& omsg, const Simple& s)19 {20 omsg << s.object << s.length << s.flag;21 return omsg;22 }

LISTING 4.1: Marshalling/unmarshalling mechanisms of a class

4.2.4 Active classes

Passing active objects as parameters requires nothing from the programmer. The se-mantics of passing an active object is similar to passing a non-active object to a C++function, i.e. the pass-by-value semantics causes a copy of the parameter to be cre-ated. The proxy object is responsible for creating the new copy of the active object.Section 4.4 describes passing active objects by value in more detail.

4.2.5 Pointers and references to active objects

It is normal C++ practice to pass object pointers and references as parameters insteadof actual objects. In fact, pass-by-reference semantics is more natural to object ori-ented programming, so object reference and pointer parameters are a rule rather thanan exception. For this reason it was considered important that pointers and referencesto active objects can be passed to other active objects.

From the programmers point of view passing active object pointers and references

35

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

1 enum Colour {RED, BLUE, GREEN};2

3 OMsg& operator << (OMsg& omsg, const Colour& c)4 {5 omsg << static cast<int>(c); // Store colour as an int6 return omsg;7 }8

9 IMsg& operator >> (IMsg& imsg, Colour& c)10 {11 int temp;12 imsg >> temp; // Read the value into an int13 c = static cast<Colour>(temp); // Convert back to a colour14 return imsg;15 }16 SPECIAL UNMARSHAL(Colour)

LISTING 4.2: Marshalling/unmarshalling of an enumeration

requires no extra work. The way this is achieved internally in KC++ is described inSection 4.3. It should be noted that passing active object pointers and references cre-ate no synchronisation problems. Each active object automatically has one-method-run-to-completion behaviour, so an active object may be used by several other activeobjects at the same time.

4.2.6 Pointers and references to non-active types

Pointers and references to non-active types can only function within a certain addressspace. Because an active object may occupy a different address space than the caller,passing pointers and references to non-active objects and data is not allowed in KC++.However, it is possible to pass a reference to a future, see Section 5.3.

Another reason not to allow pointers and references to non-active types is thefact that all active object method calls are asynchronous. Even if the separate ad-dress space issue could be solved, there would still be a problem when several activeobjects would be able to simultaneously access and modify the object at the end ofthe pointer. This is clearly unacceptable as non-active types have no built-in mutualexclusion mechanisms like active objects.

4.2.7 Futures and references to futures

As active objects, futures can be thought to occupy a “global address space” and thusthey can be passed between active objects. Future types and references to futures can

36

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

be used as active object method parameters if their underlying type is also acceptableas an active object parameter. This requirement comes from the obvious fact that theunderlying object must be marshalled and unmarshalled as part of the future mar-shalling and unmarshalling. Passing futures or future references does not otherwiserequire anything extra from the programmer.

The exact implementation and semantics of future parameters are explained inSection 5.3.

4.3 Pointers and references to active objects

Pointers and references to objects are very common in C++. In fact, in many programsobjects are accessed more often through pointers or references than by their declaredname. For this reason it is important that pointers and references to active objectsbehave just as pointers and references to normal objects, or at least imitate the beha-viour of normal pointers and references as closely as possible.

All access to active objects happens through proxy objects in the clients ownaddress space, so at first it may look like normal pointers and references to proxyobjects are enough, and no extra mechanisms are needed for pointers and referencesto active objects.

The problem lies in the fact that pointers and references to objects are very com-mon as parameters. Especially it is very common in object-oriented programming topass a pointer or reference to one object as a parameter to a method of another object.In KC++ this means that pointers and references to active objects must be allowed asparameters to an active object method. However, all proxy objects are local to eachactive object, so when a pointer or reference is passed between active objects, theproxy at the end of the pointer or reference must be duplicated in the receiver.

For example, when a pointer to active object A is passed as a parameter to activeobject B, the KC++ system must create a copy of the A-proxy in B’s address space andthen use a pointer to that proxy in B’s code. If KC++ would use normal C++ pointers toproxies, this would lead to several problems and inconsistencies:

• Two pointers should compare equal if and only if they point to the same object.This means that all pointers to the same active object in each address spaceshould point to the same proxy object. So each time an active object pointeris passed between active objects, the receiver would have to check whetherthere already exists a proxy object for that particular active object and use thatproxy if possible. Otherwise two pointers to the same active object could pointto different proxies and so not compare equal. Such “proxy search” would makepassing active object pointers very slow when there are many active objects.

• The fact that there is one active object but several copies of the proxy makes

37

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

destroying the proxies complicated, because the lifetime of the proxy is notnecessarily the same as the lifetime of the active object.

The local copies of the proxies have to be created dynamically, because theKC++ system cannot know when the copy is no longer needed. The lifetime ofthe original pointer or reference parameter is known, but of course the programmay create additional pointers and references to the proxy and the lifetime ofthese is unknown. This means that in order to destroy the proxy object, thesystem must know when there are no more pointers or references to the proxy.This cannot be achieved with C++ pointers and references.

The KC++ system solves these problems by providing its own pointers and ref-erences as template classes, and by replacing all pointers and references to activeobjects with objects of these classes. The KC++ pointer and reference classes take careof properly destroying the proxy at the end of the pointer or reference when the life-time of the pointer or reference ends. Similarly comparing two KC++ pointer objectsreturns “true” if and only if the proxies at the end of the pointers refer to the sameactive object. The following sections explain the features of KC++ pointers and refer-ences.

4.3.1 References to active objects

In C++ objects and references to them behave almost identically. Both have the sameinterface and all actions on a reference are directed to the object which the referencerefers to. In KC++ active objects are replaced with proxy objects which are practicallyjust another kind of reference objects. This makes references to proxies and proxiesthemselves resemble each other even more.

There are some differences between the behaviour of references and actual ob-jects. The most important ones are listed below. They are quite self-evident, but theyhave to be taken into account when KC++ imitates references with its own class tem-plate:

• References are usually very light-weight compared to objects. The memory con-sumption of a reference is normally only a few bytes, whereas an object mayneed any amount of memory depending on the size and number of its datamembers. This means that creating and copying a reference is much faster thandoing the same operations on the object.

• When a reference is created from an object (or another reference), it simplybinds itself to that object (or the object the reference refers to). On the otherhand, when an object is created from another object, it is copy-constructed,resulting in two objects.

• When a reference is destroyed, it simply disappears without affecting the object

38

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

it has been referring to. This is of course different from destroying an objectitself.

• A reference of type X& can refer to an object of type X or any object derived fromclass X. The static type of the reference does not tell the type of the object itrefers to.

When a KC++ program is run through the compiler, all references to active classesare replaced with an instance of a reference template. In other words. each type X&where X is an active class is replaced with type Ref<Proxy X>, where Proxy X is thetype of the proxy object.

The KC++ reference template is defined by deriving it from the proxy class it getsas its parameter, i.e. the beginning of the template definition is

template<typename T>class Ref : public T

...

By deriving each active class reference from the proxy, the reference automatically in-herits the public and protected interface of the proxy. That is enough in KC++, becauseactive objects do not have access to each others private members. This derivation isvery convenient because it allows the definition of the reference template to be placedin the KC++ library instead of having to be created by the KC++ compiler.

The derivation takes care of having the same interface in an active object referenceand a proxy. However, there are still the differences listed earlier between objects andreferences. These are taken into account in the following manner:

• KC++proxy objects are already very light-weight, containing only a pointer to thereference-counted proxy implementation. That makes the references derivedfrom the proxies just as light-weight, so in this respect KC++ reference objectsresemble C++ references.

• When a reference object is created, its constructor always gets a proxy (or an-other reference object, which is derived from a proxy) as a parameter. The con-structor of the reference calls a special constructor of the proxy base class. Thisspecial constructor binds the new proxy object to the same active object as itsparameter. This is a very fast operation as it only requires sharing the proxyimplementation and incrementing its reference count. This way creation of areference object achieves the same effect as creating a C++ reference. The spe-cial constructor is written to each proxy class by the KC++ compiler.

• When a reference object is destroyed, its base class proxy is also destroyed. Itdetaches itself from the proxy implementation, and if it was the last to use thatimplementation, it signals the active object. The active object destroys itselfwhen its own reference counts reaches zero.

39

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

This behaviour means that currently proxies and references behave identicallywhen they are destroyed. The active object is not destroyed even if its “primary”proxy (the proxy that created it) is destroyed, if there are still references to it.This behaviour makes “dangling” references impossible, but also makes KC++programs behave differently from normal C++ programs. This problem is dis-cussed in detail later in this section.

• The data inside each proxy class is identical regardless of the active class theproxy represents. The functional interface of each proxy is different (dependingon the interface of the active class), but each proxy just contains a pointer to theproxy implementation structure, which in turn just identifies the active object.

This similarity in proxies makes it possible to create a proxy of a base class typeand bind it to an active object of a derived class. The method invoker inside thederived class active object process understands all method messages created bya base class proxy, so the base class proxy can be used to invoke any base classmethod in the derived class object. Dynamic binding is implemented on theserver side, so it also works automatically.

When a KC++ base class reference object is created from a derived class proxy,everything works as it should. The base class proxy part of the reference objectshares the proxy implementation with the derived class proxy, but it poses noproblems since the implementation is identical for all proxies.

Perhaps the most common use for references is to use them as parameters, al-lowing a function to use an object created elsewhere. Normally this means that theobject is first created, then passed to a function as a reference parameter and thenlater destroyed. The function can safely use the reference because it knows that thereis always an object at the end of the reference.

The situation becomes more complex when concurrency and asynchronous meth-od calls are added to the picture. When the method and the caller of the methodexecute concurrently, it would become possible to create an object, pass its referenceto another active object and then destroy the object before the method has completedit execution. This would mean that references to an object could suddenly become“dangling” references.

Listing 4.3 on the next page shows a simple example where an active object ispassed as a reference to a method of another active object. If the code of the functionf would be executed as a normal C++ program, the program would work withoutproblems. On the other hand, when it is compiled as a KC++ program, the methodcall on line 22 becomes asynchronous, allowing both the caller and the method toexecute in parallel. Therefore it becomes possible (probable, in fact) that the actualproxy object is destroyed in f before the method has completed and stopped using theactive object. This kind of behaviour is not possible in a sequential program, makingthe problem hard to notice. There are several possible solutions to this problem:

40

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

4 class A : public Active5 {6 public:7 void action();

...8 };9 class B : public Active

10 {11 public:12 void method(A& a);

...13 };

...14 void B::method(A& r)15 {16 r.action(); // The object r refers to must exist!17 }

...18 void f()19 {20 B b;21 A a;22 b.method(a);23 // a is destroyed here24 }

LISTING 4.3: Object references and asynchronous calls

1. “Dangling” references could be allowed. In this case the programmer would justbe told to avoid such problems by designing programs carefully.

This solution is very easy technically, but it means that programmers have tomake sure that objects passed to active object methods as references are not des-troyed before the method is completed. This would require additional manualsynchronisation in the program.

2. An active object could “stay alive” until all references to it are destroyed, evenif the “primary” proxy object which created the active object is destroyed beforethe references.

This alternative makes sure that there are no “dangling” references, but on theother hand it means that active objects are not necessarily destroyed whenthe programmer expects. To the client code the proxies should be identical to

41

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

the actual active objects. This makes letting the active objects exist after their“primary” proxy has been destroyed significantly different from the way thingsare done in normal C++.

There is another problem in addition to the inconsistency described above. Cyc-lic references would create situations where two active objects refer to eachother and for this reason neither is ever destroyed. This problem is identical toproblems in all reference-counting based garbage collection systems.

3. Destruction of the “primary” proxy could be delayed until all references to theactive object have been destroyed. This would mean pausing the execution ofthe thread owning the primary proxy until all references have been destroyed.

This solution would make destroying active objects through proxies behave thesame way as destroying normal objects. However, cyclic references would stillbe a problem and cause deadlocks instead of object leaks.

4. In addition to the solutions listed above, it would also be possible to modifysome mark-and-sweep garbage-collection algorithms to find out whether an act-ive object could be destroyed safely. However, distributed garbage-collectionsystems are usually very heavy-weight. The order of destruction is also import-ant because of destructors, and many garbage-collection systems do not takethat into account.

The current version of KC++ currently uses solution number 2 — destroying activeobjects only after all references to them are destroyed. However, it seems that cyclicreferences are so common in C++ programming that they cause more problems than“dangling” references. Chapter 8 briefly discusses possible future changes to KC++.

4.3.2 Pointers to active objects

Pointers to active objects are in many ways similar to references. Pointers in C++ arealso very light-weight, creating them does not create actual objects, and a pointer to abase class can also point to an object of a derived class. However, pointers have someadditional characteristics:

• A pointer to an object can be obtained by creating an object dynamically withnew or by applying the “address-of” operator & to an object or a reference.

• A pointer can be empty (or “null”), i.e. not pointing to anything. The user cantest this by comparing the pointer to zero (if (p == 0)) or by directly using thepointer as a condition (if (p) or if (!p)).

• Objects created dynamically with new can (and should) be destroyed explicitlythrough a pointer with delete.

• The object is accessed through the pointer using operators * and −>.

42

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

• If a pointer points to an object in an C++ array, the pointer can be moved insidethe array using pointer arithmetics (operators ++, −− etc).

The KC++ compiler replaces all pointers to active objects with instances of a point-er template. Each occurrence of type X*, where X is an active class, is replaced with atype Ptr<Proxy X>, where Proxy X is the type of the proxy object.

The pointer template is defined as a class which internally maintains its ownproxy object which in turn refers to the actual active object. The pointer characterist-ics are implemented in the following manner:

• A KC++ pointer object can be initialised from a C++ proxy pointer returned bythe new operation. In addition to this, all proxy classes implement their ownoperator & which return a KC++ pointer object instead of a C++ pointer.

• If the pointer object is empty, it simply contains no proxy object. The pointer ob-ject implements the “logical not” operator ! and a conversion to a void* pointer.With these the user can test the emptiness of a pointer object in the same wayas with normal C++ pointers.

• The C++ delete operator cannot be used, because it requires a C++ pointer as itsparameter. For this reason the KC++ compiler replaces all active object pointerdelete operations with a call to the pointer object’s method destroy(), whichsimply destroys the pointer’s proxy object and empties the pointer object.

• The pointer template classes implement their own operators * and −> whichreturn a reference to the pointer’s proxy object. This way an active object canbe accessed through a pointer object in the same way as through a normal C++pointer.

• The current version of KC++ does not support pointers to active object arrays, sopointer arithmetic is not supported. Allowing pointer arithmetic would meanthat if a pointer to an array would be passed as a parameter between activeobjects, the KC++ would have to copy the whole array from one active object toanother, as there would be no way to know whether the parameter pointer islater moved inside the array.

When a pointer object is created from an active object proxy, it creates a localduplicate proxy object referring to the same active object. When a pointer object isdestroyed, it automatically destroys its local proxy.

Each pointer object creates its own local proxy because of parameter passing. Fig-ure 4.1 on the following page shows a situation where a pointer to an active object ispassed as a parameter between two active objects. In this kind of situation the receiv-ing active object does not necessarily have any proxy for the active object the pointerpoints to. For that reason creating a copy of the pointer object in the receiving end

43

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

a.Method1(&b);

objectProxy

A

ID=1

}; void Method1(B* p); ...{ : public Activeclass A

class B : public Active...

A

Activeobject

Proxyobject

ID=2

B

B

Activeobject

Proxyobject

B

ID=2

Method message p(temp)

...

"User process"

...

Active object ID=1

Active object ID=2

a

b

Pointer objects

Method2

Method1

Method3

Method1

Method2

Method3

Method1

Method2

Method3

Method1:B* 2

FIGURE 4.1: Passing an active object pointer between active objects

has to include creation of the proxy also. The proxy must also be destroyed properlywhen the pointer object is destroyed.

The easiest way to make sure the additional proxies are always created and des-troyed properly is to let each pointer maintain its own proxy object, as current KC++does. This means that creating a pointer means the additional overhead of creatingan extra proxy even when the pointer object could point to an already existing proxy.This overhead is very small, however. Each proxy object just contains a pointer to areference counted proxy implementation, so copying a proxy just means copying theproxy implementation pointer and incrementing the reference count.

4.4 Passing active objects by value

When a function in C++ takes an object as a normal parameter (i.e. not a reference ora pointer to an object), pass-by-value semantics is used. This means that a copy of theactual parameter object is created in the function. This copy is initialised using its

44

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

copy constructor, using the original object as a parameter. This copy of the parameterobject is automatically destroyed when the function returns.

KC++ tries to follow normal C++ semantics as closely as possible. This means thatpassing an active objects as a parameter should have a similar effect as passing anormal object. When an active object is passed by value, a copy of the active object iscreated and constructed using its copy constructor.

When the function to be called is a normal function or a member function of anon-active object, the normal C++ pass-by-value mechanism is sufficient to take careof creating the copy. The KC++ compiler changes the parameter type of the functionfrom the active class to a proxy class. When the function is called, the creation of the“copy” parameter proxy using the copy constructor automatically triggers the creationof another active object.

The situation is a little bit more complex when an active object is passed as a valueparameter between active objects. Figure 4.2 on the next page shows the situation ina case where an active object b is passed as a value parameter to method method1()of another active object a.

1. First the proxy of the active object b is passed as a value parameter to the re-spective method method1() in the proxy of a. At this point the normal C++ pass-by-value creates a copy of the b proxy.

2. The new proxy creates a new active object and invokes its copy constructor.The copy constructor receives a reference to b as a parameter — this uses theactive object reference parameter mechanism described in Section 4.3.1.

3. Next this new proxy is marshalled into the method message. In practise thisjust means storing the ID of the active object and signalling the active objectto increase its internal reference count. Then the method message is sent toa. After this the proxy method is completed and the local parameter proxy isautomatically destroyed. This doesn’t destroy the actual active object, becauseit’s reference count was already increased during marshalling.

4. When the method message is received in a, the method invoker unmarshals themessage and creates a local proxy object referring to b. This proxy is then passedas a parameter to the actual active object method. At this stage pass-by-valuemust not be used, because it would result in the creation of another copy. TheKC++compiler solves the problem by replacing all active object value parameterswith reference parameters in the actual active object method implementations.

5. When the method completes, the method invoker destroys the parameter proxy,which signals the copy of b, which in turn destroys itself.

Passing an active object as a value parameter is clearly a very heavy-weight op-eration as this example shows. Objects are quite seldom passed by value in object-

45

CHAPTER 4. PASSING PARAMETERS TO ACTIVE OBJECTS

a.Method1(b);

objectProxy

A

ID=1

}; void Method1(B b); ...{ : public Activeclass A

class B : public Active...

A

Activeobject

Proxyobject

ID=3

B

B

Activeobject

Active object ID=2

B

Activeobject

Active object ID=3Proxyobject

ID=2

Base

Proxyobject

B

ID=2

Proxyobject

B

ID=3

Method message

...

"User process"

...

Active object ID=1

a

b

1.

1.

2.

3.

4.

Method2

Method1

Method3

Method1

Method2

Method3

Method1

Method2

Method3

Method1

Method2

Method3

Method1:B 3

FIGURE 4.2: Passing an active object by value

oriented programming, but if it is needed, value passing has the same semantics inKC++ as in normal C++.

46

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

Chapter 5

Asynchronous method calls andfutures

Asynchronous method calls are a common way of achieving concurrency in con-current object-oriented systems, including KC++. Proxy objects make asynchronousmethod calls very easy to implement, as the proxy object can simply send a methodrequest to the active object and then return. However, in many languages (includ-ing C++) methods are also allowed to return values, and these return values force thecaller and the active object to synchronise with each other at some point, so that theresult of the method can be transferred from the active object to the caller.

Some object-oriented methodologies (the one presented in [Meyer, 1997], for ex-ample) divide all methods to two categories — commands which are allowed tochange the object but not return any values and queries which are only allowed returndata from the object but not modify it. As most computationally intensive function-ality often resides in commands, it is very natural to add concurrency only to thesemethods. This solution has the additional benefit that return values are not a problemas all the value returning methods — queries — are still synchronous.

Some concurrent C++-based systems like UC++ use the same solution and restrictasynchronous operation only to methods with void return types. However, the di-vision of methods to commands and queries is not natural in C++, where it is quitecommon to return values from computationally intensive operations.

In other systems a single method call can be either synchronous or asynchron-ous. The synchronous and asynchronous method calls have a different call syntax,letting the programmer decide which way to call the method. ABC++ is an exampleof this kind of a language. There is a slight conceptual problem with this approach:synchronous and asynchronous methods have different completion characteristics,and therefore it is questionable whether a single method should (or can) be bothsynchronous and asynchronous depending on the call syntax.

47

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

An asynchronous method call is clearly the less restrictive mechanism as it doesnot say when the method actually gets completed. A synchronous call can then bethought as an asynchronous call followed by a wait for completion. This does notmake the synchronous call any less efficient as a similar return value message isneeded in both cases to get the return value back from the active object. KC++ usesthis approach and makes all active object method calls automatically asynchron-ous. The user can then decide whether to wait for the return value immediately (asynchronous-looking call) or to leave the return value to be received later (a “real”asynchronous call). Both these alternatives have a very natural-looking syntax, soC++ programmers should “feel at home” with both synchronous and asynchronouscalls.

The fundamental mechanism behind asynchronous method calls in KC++ are fu-tures. These are “wrappers” first introduced by Halstead in Multilisp [Halstead, 1985].They are widely used in several concurrent C++ systems, like ABC++ [O’Farrell et al.,1996], sometimes under different names like “wait-by-necessities” in C++// [Caromelet al., 1996] or “IOUs” in the Threads.h++ library [Thompson, 1998].

From the caller’s viewpoint futures can be seen as a “placeholder” for a value thatwill be eventually received from an asynchronous method call. From the method’sviewpoint a future is a “promise” to send a value to the caller.

In KC++ futures are a template class which can be used to instantiate “place-holders” for values of almost any type. These values do not have to be available at thetime when a future is created, but they may become available later, for example whena method completes its execution. In many other systems futures are only used to re-ceive return values from asynchronous method calls, but the KC++ future types canbe used fully as “first-class citizens” — they can be passed as “delayed parameters”to other active objects or to receive additional return values using future referenceparameters.

The KC++ futures do not have to get their values from method return values. Pro-grammers can create future sources which act as the origins of the values the futuresrepresent. This way the programmer can use futures for explicit synchronisation anddelayed value passing.

5.1 Principles of futures in KC++

The KC++ future type is a simple template type whose only parameter tells the “un-derlying type” of the future, i.e. what type the future is meant to represent. The nameof the template is Future, so a future type for integers is marked Future<int>, and afuture type for string objects is Future<string>.

In order to be able to create futures with a certain underlying type, this type hasto satisfy certain requirements:

48

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

• The type must have a working copy constructor

• The type must be allowed as a parameter between active objects. This meansthat it must have a marshalling operator (<<). It must also have an unmar-shalling constructor or, an unmarshalling operator (>>) and a default con-structor (see Section 4.2.2).

• If any of the comparison operators ==, !=, <, >, <=, or >= is defined for thetype, then for all values t of type the expressions t==t, t<=t and t>=t shouldalways be true and similarly t!=t, t<t and t>t should always be false. Thisrequirement is needed so that future comparisons can be optimised in certainsituations. However, it is very unlikely that any reasonable type would not sat-isfy this requirement.

If a type T meets these requirements, then futures of type Future<T> can be cre-ated. As active object method calls return futures as their return values, the require-ments also apply to any type that acts as a return type of an active object method.

Once a future has been created (either explicitly by the programmer or implicitlyby the KC++ system), it acts as a placeholder for a value and can be used to access thisvalue. A future can be in two states, pending or ready. A pending future has not yetreceived its value. When the value is received, the future becomes ready.

If a future is still pending (i.e. its value is not yet available) when an attempt ismade to access the value, the access methods in the future wait and suspend theexecution until the value becomes available. This way the program can continue itsoperation normally until it really needs the return value of an asynchronous method,at which point the future automatically forces the program to wait for the value.

5.1.1 The interface of futures

A future type Future<T> has the following interface:

• Constructors: The constructors allow a future to be created with a known valueor from another future. They are explained in more detail in Section 5.1.4.

• Assignment: A value of type T or another future can be assigned to a future.Assignment is discussed in Section 5.1.5.

• Destructor: When a future is destroyed, it automatically takes care of the de-struction of it’s value, if any. Destruction of futures is covered in Section 5.1.6.

• bool isready() const. A future can be asked about its readiness with the syntaxf.isready(). The function returns true if the future is ready and has alreadyreceived its value, false if it is still pending. This function should only be usedto check the status of a future occasionally. It should never be used in a tightloop to wait for the value (the method wait is for this purpose).

49

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

• void wait() const: If the status of a future f is ready, f.wait() returns immedi-ately without doing anything. If f is pending, the call halts the execution of thecurrent thread until f receives the value and becomes ready. Quite often a fu-ture can be used without calling wait explicitly, as the value accessing methodscall it automatically.

• const T& value() const: The call f.value() first calls wait to make sure f isready, and then returns a constant reference to the future’s value. This functionis usually called internally by the type conversion function described below,but it can also be used to explicitly ask for the value of a future.

• operator const T&() const: When a type conversion from a future to its under-lying type is performed, the conversion function simply calls value to wait forthe value of the future and then return it.

• bool isSame(const Future<T>& ): Two futures can share a value (regardless ofwhether the value is pending or not). This happens when a future is assignedto another future or a copy of a future is created (see Sections 5.1.4 and 5.1.5).For two futures f1 and f2, the call f2.isSame(f1) returns true if the two futuresshare their value, and false otherwise. This test can be performed on pendingfutures without waiting for the value.

• Comparisons: If the underlying type supports comparisons (==, !=, <, >, <=, or>=), these comparisons can also be used on the future itself. The comparisonsinvoke the same comparison operation on the values of the futures, waiting forthe values to become available, if necessary.

There is one important exception: If two futures share the value (isSame wouldreturn true), then ==, <= and >= immediately return true and !=, < and >

return false. This happens without waiting for the value to become availableor invoking the comparison operations on the already available value (whichwould only compare the value with itself). The advantage of this behaviour isthat if two pending futures share the same value, their equality can be resolvedwithout waiting.

5.1.2 Using futures

The KC++ future mechanism is designed with two goals in mind: First, it should berelatively easy to add concurrency to an existing program by replacing objects withactive objects and necessary normal variables with futures. This should be possiblewith as few limitations as possible. Secondly, the programmer should be providedwith an interface to manually control asynchronous operations, if needed.

The first goal is mostly achieved through the constructors and the assignment,type conversion and comparison operators. These make it possible in many cases touse a future variable almost as a variable of the underlying type. A future variable can

50

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

be initialised with a value of the underlying type, or such a value can be assigned toa future variable later. The value of a future can be read from a future automaticallythrough its type conversion operator, and two futures can be compared, if such acomparison exists for the underlying type. This way, if no asynchronous operationsare invoked, using future variables in place of normal variables does not in manycases affect the behaviour of the program at all.

If an asynchronous operation results in a pending future and that is used in anexpression where a value of the underlying type is needed, the execution of the threadis automatically suspended until the value of the future is received, and then thevalue is used in place of the future. The implicit conversion makes it often possible toreplace variables of type T in existing code with futures of type Future<T>, allowingthe program to automatically benefit from asynchronous method calls.

Listing 5.1 on the following page shows a small program where all instances of thetype int are replaced with futures, without affecting the program in any way (exceptfor a small performance cost). Of course such a change is absurd without addingconcurrency, but it demonstrates how close future variables are to normal variables.

If active objects are used to add concurrency to the program, the program codewould have to be changed very little. In this example, classes Generator and Functorcan be made active by deriving them from class Active. After this modification theirmethod calls are automatically asynchronous and return futures. Other changes arenot necessary.

Because futures can be assigned and copied freely without causing synchronisa-tion, the program in Listing 5.1 runs without synchronisation until the future valuesinside the vector are added up in the C++ library function accumulate. There a vari-able of type int is created (the type is dictated by the type of the initial value 0), andeach value in the vector is added to the variable. This expression int + Future<int>causes the compiler to invoke a type conversion from the future type to integer, whichcauses synchronisation and halts the execution of the program until each value in thevector becomes available.

Although the automatic synchronisation during type conversion is often enough,sometimes it is desirable to explicitly control synchronisation. This can be done withmethods isready and wait. If synchronisation with the completion of an asynchron-ous operation is wanted before the return value is actually needed, calling wait onthe return value future halts the program until the method has completed. This maysometimes be necessary to coordinate the execution of active objects, or to simply“catch up” with active objects to prevent an excessive amount of pending operations.

The polling function isready is probably less useful than wait. With it a programcan start an asynchronous operation and periodically check whether the operationhas been completed, performing some other tasks while “waiting.”

Explicit synchronisation becomes more important when future sources are usedin the program to achieve user-controlled synchronisation and notification. This sub-ject is discussed in detail in Section 5.5.

51

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Generator2 {3 public:4 int firstiter();5 int enditer() const;6 int nextiter();

...7 };8

9 class Functor10 {11 public:12 int transform(int iter) const;

...13 };14

15 typedef Future<int> Fint;16

17 Fint accumulate(Generator& g, Functor& f)18 {19 vector<Fint> fvec;20

21 Fint iter = g.firstiter(); // Conversion to future22

23 // Generate values to an array24 while (iter != g.enditer())25 {26 Fint value = f.transform(iter); // Conversion both ways27 fvec.push back(value);28 iter = g.nextiter();29 }30

31 // Add values using STL’s accumulate32 Fint result = accumulate(fvec.begin(), fvec.end(), 0);33 return result;34 }

LISTING 5.1: Example where ints have been replaced with futures

52

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

5.1.3 Futures as constant references to “system-wide” data

Values of futures are normally created in one active object and later used in another.Another way to look at the same thing is to think of these values as “system-widedata” which is available to every active object in the program. In this way of think-ing futures are simply local objects used to access the global data. Of course theconcept of system-wide shared data is not directly possible in a system with no sharedmemory, but the concept is quite appealing in a system like KC++, where active objectsalready represent “system-wise objects.”

If values of futures are considered system-wide, the way these values can be mod-ified becomes important. If no shared memory is available, the values would eitherhave to reside in a “server object”, which would serve read and write requests fromactual future objects, or each future could keep its own copy of the data and all modi-fications would have to be sent to each future object “sharing” the value. Parametricshared regions in ABC++ [O’Farrell et al., 1996] are a combination of these approaches.There a server keeps a “master copy” of a shared value and takes care of distributingchanged values between clients.

Both these approaches have their problems. The server approach would practic-ally make each future value an active object, and this would mean that even readingthe value of a future (or asking whether the value exists) would include the over-head of message passing etc. between the future server and the reader. When futuresare used in active object return values, this would increase the amount of messagepassing considerably, making the approach impractical.

The approach where each future object keeps a copy of the value is very efficientwhen only reading is required. However, when the value is modified through onefuture object, all other future objects have to be notified of the new value. This causesa lot of message passing, and additionally every future object would have to know allother future objects sharing the value in order to be able to send message to them.This would increase the size of future objects, and each time a new future object iscreated, every other future would have to be notified of this event, too. All this makesthe approach quite impractical, too.

The problem of copying the value becomes much easier if the value cannot bechanged after it has become known, i.e. the value can only be initialised but notmodified. In this case the creator of the value has to send a copy to pending futureobjects, but after that there is no need for communication between the futures. Onlythe creator of the value has to know where to send it, otherwise the futures do nothave to be aware of each other. Figure 5.1 on the next page shows together all thethree different future implementation possibilities.

KC++ uses the “constant copy” approach to implement futures. Making future val-ues unmodifiable is quite justifiable, because the principal reason for futures is returnvalue passing, and there is no reason to “change the return value” after a method hasbeen completed. In practise, a future object in KC++ can be thought as a “constant

53

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

FF

F

value

S

Future server

(a) Server

FF

FS

valuevalue

value

(b) Modifiable copy

FF

FS

valuevalue

value

(c) Constant copy (KC++)

FIGURE 5.1: Different ways to implement futures

reference” to a system-wide value. The analogy is very concrete because the typeconversions in future classes allow futures to be implicitly converted to a constantreference to their value.

Each time a pending future is copied from one active object to another (throughparameter passing or as a return value), the KC++ run-time system remembers thatwhen the value of that future becomes available, the value must be forwarded tothe other active object. When the value finally is available (when a method returnsa value or when a future source is bound to a value), all appropriate local pendingfutures in the same active object are first given the value. The run-time system thenchecks to see where these futures have been copied, and sends a message containingthe value to other appropriate active objects. After this the run-time system can dis-card the forwarding information. The run-time system in other active objects does the

54

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

same, and so the value of the future propagates from one object to another, followingthe same path as the original pending future.

There are some differences between constant references and futures, however.First, a new value (pending or not) can be assigned to a future. This causes the futureto “bind” itself to the new value and abandon the old one. This is not possible withreferences, which always refer to the same value for their entire lifetime. This differ-ence was introduced to KC++ in order to make it easier to replace normal variableswith futures. Assignment is a common operation with normal variables, so disallow-ing it in futures would have restricted their use.

Another difference between futures and references to a system-wide value is thata local copy of the value is kept in each active object (in fact, several copies may bekept if the same future is received several times). This can only create problems ifcopies of the value do not behave identically, which is possible if the value is an non-active object. For this reason it is important that if an object is used as a future value,then marshalling and unmarshalling it should produce an identically behaving copy.The KC++ parameter passing system relies on the same thing, so this is not a newrestriction in KC++.

5.1.4 Creating futures

Future objects can be created in several ways. Probably the most common way is to geta future object as a return value from an active object method call. This is explainedin detail in Section 5.2. Another way is to create futures from future source objects,which are discussed in Section 5.5. In both these cases the resulting future is probablypending, and it will get its final value later.

A third way to create a future object is simply to create it as a normal variable inthe program. The future template class provides several constructors for this:

• Future<T> f; The default constructor creates a future which contains a valueT(), i.e. the default value of the underlying type, created with its own defaultconstructor. For example, Future<int> if; creates a future with value zero,and Future<string> sf; creates a future with an empty string as its value.

This constructor can only be used if the underlying type has a default con-structor, otherwise a compiler error is generated.

• Future<T> f(/* parameters */); If the future’s constructor is given paramet-ers, it creates a future which contains a value initialised with the same para-meters. This way Future<int> if(3); creates a future with value 3. SimilarlyFuture<string> sf(10,’x’); creates a future which contains string(10,’x’)— a string of ten “x”s.

In the current KC++ this method of construction can be used if the underlyingtype’s constructor needs at most 15 parameters. However, this limit could beeasily increased, if necessary.

55

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

• Future<T> f2(f1); (where f1 is another Future<T>) The copy constructor canbe used to create a copy of another future of the same type. If the original futurecontains a value, the copy shares the same value, i.e. a copy of the value is notcreated. If the original future is pending, the new future becomes also pending.When the value becomes available, both the futures share the received value.

Sharing the value does not cause problems because the value inside a futurecannot be modified. This way a programmer cannot accidentally change thevalue of one future by modifying another future sharing the same value. Sharingis automatically broken if either of the futures gets assigned a new value.

The constructors above make sure that a programmer cannot explicitly create“loose-ended” futures which would never get a value from anywhere. The first twoconstructor types create futures which already contain a value. The copy constructorcreates a pending future only if the original future is already pending. If the originalfuture is pending, it must have been originally created by an asynchronous methodcall or a future source, or it is itself a copy of such a future. If all methods eventuallycomplete their execution, the return value futures eventually get their value. Simil-arly, if a future source gets eventually bound to a value or gets destroyed, all futurescreated from it get a value. This way futures created by the programmer cannot causedeadlocks by themselves, if the rest of the program works properly.

5.1.5 Assigning futures

Future types also have assignment operators which make it possible to assign a newvalue to a future. When a new value is assigned, the old value gets destroyed un-less there are several futures sharing the same (possibly pending) value. In this casethe assignment “detaches” the future from the shared value before assignment. Sec-tion 5.1.6 discusses the situation where a pending future value gets destroyed beforereceiving its value.

Future types have the following assignment operators (below, all objects withnames beginning with f represent futures of type Future<T> and objects with namesbeginning with v represent values of type T):

• f = v; When a simple value of the future’s underlying type is assigned to thefuture, the future discards its old value and creates a new value by storing acopy of v. After the assignment the future is not pending regardless of whetherit was pending before the assignment.

It should be noted that the value v is not assigned using its own assignmentoperator, but rather a copy of it is created using the copy constructor. This isthe only option as the semantics of KC++ does not allow changing an existingfuture value, making the use of assignment operator impossible.

• f2 = f1; When a future is assigned to another future of the same type, the targetof the assignment discards its old value and starts sharing the value of the other

56

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

future. If f1 is pending, f2 becomes pending too, and when the value becomesavailable, both futures share the same received value. This makes the behaviourof assignment compatible with the behaviour of the copy constructor.

Like the construction of new futures, assignment of futures makes sure that theprogrammer cannot end up with a pending future which would never receive anyvalue.

Because assigned and copied futures share their value, future assignment andcopy construction are very efficient operations. The underlying value does not haveto be copied and the internal implementation of futures themselves is so simple thatits assignment and copying does not take much time. Normal C++ parameter passinguses the copy constructor, so passing futures by value inside an active object is alsovery efficient.

5.1.6 Destroying futures

When a future object gets destroyed, its destructor takes care of necessary actions.These actions depend on whether the future is sharing a value and on whether thefuture is still pending or not.

If the future to be destroyed shares its value (pending or not) with another fu-ture, it only detaches itself from the shared value. In this case no other actions arenecessary.

If the future is not sharing its value with any other future and if it is not pending,its destructor destroys the future’s value. If the value is an object, this includes callingthe object’s destructor.

The situation becomes more complex if the future is still pending and not sharingits value with other futures. In this case simply destroying the future would meanthat there would not be any place where to receive the value when it finally becomesavailable. This would cause several kinds of problems:

• If the future’s underlying type is a simple non-object type, it would not matterwhether the future’s value would be received at all. The message containingthe destroyed future’s value could simple be discarded by the KC++ run-timelibrary. The program could not in any way detect that the future’s value was notactually “received” at all.

However, futures’ underlying types may also be object types. In this case theconstruction of the future’s value (and its destruction) are events which maycause side effects outside the object itself. This means that the arrival of thefuture’s value may be detected by the program even when the actual futurehas been destroyed. For this reason the future’s value should be “received” andconstructed properly even if the future itself no longer exists.

57

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

• If a pending future would simply be destroyed, it would mean that the threadowning the future would not expect the message containing the future’s valuelater when it arrives. This could be solved in the KC++run-time library by simplydiscarding all messages whose recipient future does not exist. However, if allunidentified messages would be silently discarded, possible bugs in the KC++implementation might go unnoticed.

• The KC++ system does not necessarily have to use message-passing mechanismto send values of futures from one active object to another. In a shared-memorysystem it is quite reasonable to use shared memory and semaphores to passthe future values between execution threads. In this kind of an environmentit would be impossible (or at least disastrous) to simply destroy a pending fu-ture and its underlying semaphores and memory, as the sender of the future’seventual value would still need to access the semaphore.

The KC++ run-time system solves all the problems mentioned above by keeping alist of all pending futures in each execution thread. This list is simply a list of actualfuture objects, each one sharing its value with a “real” pending future in the program.

When a future receives it’s value, the run-time system finds the correct pendingfuture from the list, unmarshals the value inside the future and then destroys thefuture from the pending list. Because of value sharing, all appropriate futures in theprogram have automatically “received” the value. This sequence of events is shownin Figure 5.2 on the following page.

This mechanism makes sure that a future is never destroyed while it is pending.Every pending future in the program is always sharing its value — at least with afuture in the pending list. If the actual future gets destroyed before its value is re-ceived, it detaches itself and leaves the future in the pending future list to wait forthe value. When the value finally arrives, the KC++ run-time system finds the futurein the pending list and unmarshals the value inside that future. After this the run-time system destroys the future from the pending list, but at this time the future is nolonger pending and can be destroyed normally. This scenario is shown in Figure 5.3on page 60.

The mechanism described above makes sure that all futures will eventually re-ceive their value (causing the execution of the value objects constructor and de-structor), even if the actual futures are destroyed before the value arrives. The run-time system also refuses to destroy an active object until all its pending future valueshave been received.

5.2 Futures as return values

The main use of futures is to allow asynchronous method calls with return values. Ifan active class has a method with return type T, the proxy class created by the KC++

58

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

Future

Future

pending list

pending

value

(a) Pending

Future

Future

pending list

value

value

value message

(b) Receiving

Future

pending list

value

value

(c) Not pending

Future

pending list

value

value

destroy

destroy

(d) Destruction

FIGURE 5.2: Destruction of a future after receiving the value

compiler has a corresponding method returning a future of type Future<T>. Thereare three exceptions to this:

• Methods returning nothing (i.e. void) also return nothing in the proxy. Becausethere is no return value, no futures are needed. However, this also means thatthe caller cannot synchronise with the completion of such a method, i.e. void-returning methods cannot be called synchronously. If such a synchronisation isneeded, the original method should return Future<void> (see the item belowand Section 5.4).

• If the original method already explicitly returns a future type, this type is pre-served in the proxy. This is sensible as following the normal rule would resultin a “future future type,” which is seldom useful (but possible, nevertheless).The meaning of explicit future return types is explained in Section 5.2.3.

• If the return value is an active object or a pointer or reference to such an object,

59

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

Future

Future

pending list

pending

value

(a) Pending

Future

Future

pending list

pending

value

destroy

(b) Detachment

Future

pending list

value

value

value message

(c) Receiving

Future

pending list

value

value

destroy

(d) Destruction

FIGURE 5.3: Destruction of a future before receiving the value

no future is returned. However, the normal type changes from active classes toproxies, from pointers to KC++ pointer classes and from references to KC++ refer-ence classes are performed. KC++ proxies, proxy pointers and proxy referencesare implemented internally using futures (see Section 6.2.2), so no explicit fu-tures are needed.

Even though methods of the proxy return futures, the methods in the actual activeobject continue to return the same return values as before. Listing 5.2 on the followingpage shows a definition of an active class and the compiler-generated definitions ofthe corresponding proxy class and active class.

When an active object method is called through a proxy, the proxy creates apending future object, sends its ID to the active object in the method message, andthen returns the future object as the method’s return value.1 After receiving the

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .1This is the only case in KC++ where a pending future originates in a different object than the object

where its value is finally given.

60

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

. . . . . . . . . . . . . . . . . . . . . . . . . . . Original active class . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Example : public Active2 {3 public:4 int giveInt();5 Future<double> giveDouble(); // (Section 5.2.3)6 Future<void> syncVoid(); // (Section 5.4)7 Example& operator =(const Example& e);

...8 };

. . . . . . . . . . . . . . . . . . . . . . . . . . . . Generated proxy class . . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Proxy Example :2 public Proxy3 {

16 public:17

18 Future< int > giveInt();19 Future<double> giveDouble();20 Future<void> syncVoid();21 Proxy Example& operator=(const Proxy Example&);

...42 };

. . . . . . . . . . . . . . . . . . . . . . . . . . . Generated active class . . . . . . . . . . . . . . . . . . . . . . . . . . .1 class Example : public Active2 {

11 public:12 int giveInt();13 Future<double> giveDouble(); // (Section 5.2.3)14 Future<void> syncVoid(); // (Section 5.4)15 Ref< Proxy Example > operator =(16 const Ref< Proxy Example > e);

...17 };

LISTING 5.2: Definitions of an active class and its proxy class

61

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

method message, the active object’s method invoker executes the method. When themethod completes, the method invoker takes the return value and binds it to the re-turn value future. After that the KC++ run-time system takes care of forwarding thevalue to the caller.

5.2.1 Asynchronous method calls

As mentioned before, all KC++ active object method calls are implemented as asyn-chronous calls, with proxies returning futures as return values. Therefore this sectionmainly discusses a few ways of utilising asynchrony and futures, as well as pointingout a few special cases.

In the simplest case the programmer simply calls an asynchronous method andthen synchronises with the completion of the method later with wait (or polls thefuture with isready):

Example e;Future<int> fi = e.giveInt();

...fi.wait(); // Synchronise

Alternatively the synchronisation may be implicit when the return value future isconverted to its underlying type:

void function(int i);Example e;Future<int> fi = e.giveInt();

...function(fi); // Conversion & synchronisation

To benefit from asynchronous calls, the synchronisation should happen as late aspossible, in order to minimise the time the caller has to wait for the actual returnvalue to arrive. Assigning futures to each other and copying them does not requiresynchronisation, so futures should usually be used as much as possible. For example,in the previous example synchronisation happens before function is called. However,it is quite probable that this function does not use the value of its parameter immedi-ately, so it could be sensible to change the parameter type to a future as well. Copyingfutures locally is very fast (see Section 5.1.4), so passing a future as a parameter in-stead of an integer does not usually add an unacceptable overhead to the programexecution:

void function(Future<int> i);Example e;Future<int> fi = e.giveInt();function(fi); // No synchronisation

62

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

KC++ futures satisfy both the “CopyConstructible” and “Assignable” requirements,which ISO C++ containers require from their elements [ISO, 1998, Ch. 23]. There-fore it is safe to store futures in containers like vector or map. The speed of futurecopying and assignment make operations on such containers fast even if copying andassigning the return values themselves would be slow. No synchronisation is neededas long as the container operations do not need the actual value of the future. List-ing 5.3 contains a function which creates (and immediately discards) ten Exampleobjects, stores the return values in a vector, copies them to a list in reverse order, andfinally synchronises with all the values using the function for each in the ISO C++algorithm library.

It is common in C and C++ to call a function returning a value without using thevalue, i.e. discarding the value completely. The C++ syntax is designed to allow this— the return value of a function or a method does not have to be assigned or copiedanywhere. Doing this on futures poses no problems or dangers. The proxy methodalways returns a future (unless the method returns void). If this return value object isnot used in the program, its lifetime ends automatically at the end of the full expres-sion where the method was called (usually at the next semicolon). Then the returnvalue future is destroyed without synchronisation as described in Section 5.1.6. TheKC++ run-time system still knows about the future and receives the value of the future

1 void example()2 {3 typedef Future<int> Fint;4 vector<Fint> v;5 for (unsigned int i=0; i<10; ++i)6 {7 Example e; // Create new active object for each round8 v.push back(e.giveInt());9 // Each active object is discarded at the end of round

10 }11

12 list<Fint> l;13 // Insert elements to list in reverse order14 copy(v.rbegin(), v.rend(), back inserter(l));15

16 // Synchronise with all elements and then display them17 for each(l.begin(), l.end(), mem fun ref(&Fint::wait));18 copy (l.begin(), l.end(), ostream iterator<int>(cout));19 }

LISTING 5.3: Futures with ISO C++ containers

63

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

when it finally becomes available.

Example e;e.giveInt(); // Discards the return value

5.2.2 Synchronous method calls

Although all active object method calls are asynchronous, synchronous calls can beeasily simulated by simply not using futures. The return value future returned by theproxy object is then implicitly converted to the original return value type, causingsynchronisation immediately after the call:

Example e;int i = e.giveInt(); // ‘‘Pseudo-synchronous’’ call

The benefit of this “pseudo-synchronous” call technique is that it does not re-quire any additional syntactic trickery when the method is called. It also makes theresulting code smaller as the KC++ compiler does not have to create different code forasynchronous and synchronous methods.

The simulated synchronous call does not cause any performance penalty either.Asynchronous call followed by synchronisation requires two messages between theobjects: the method message to the active object and the future value message backto the caller. There is no way to accomplish a value-returning method call with lessthan two messages, even if a dedicated synchronous method call mechanism wasimplemented. The only additional overhead is the creation and destruction of futureobjects, but this overhead is likely to be small compared to the unavoidable overheadof passing information between two active objects.

There is one limitation caused by always asynchronous method calls. If a methodreturns nothing (i.e. void), the proxy method does not return anything either. Nor-mally this is what is wanted, because there is no return value to synchronise to.However, sometimes a program may want to synchronise with the completion of amethod, even if no return value is needed. Many future-based systems require thatsuch methods return a “dummy” return value (a boolean value true is often used). InKC++ such dummy return values are not needed, because the designer of the activeclass can write methods with return type Future<void>. This explicit future returnvalue allows the caller to synchronise with the completion of the method withoutactually receiving any “value.” These void-futures are explained in Section 5.4.

5.2.3 Explicit future return types

When a future is received as a return value from an active object method call, itnormally receives its value when the method completes its execution. In this casethe caller always synchronises with the completion of the method. In many situations

64

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

this is too restrictive. Clearly the synchronisation cannot occur before the methodcompletes, as there is no return value until that point. However, it may be useful tosynchronise to some event which happens after the method has been completed.

For example, in an active object representing an empty buffer, the method getmight return a future which does not get its value when the method completes, butonly after the put method has been used to add an element into the buffer. To achievethis kind of functionality, an active object method must be able to return a valuewhich is not necessarily known when the method is completed.

The KC++ system solves this by allowing explicit use of futures as return types inactive classes. When an active object method return type is not a future, the corres-ponding proxy class returns a future for that type, and the future gets its value whenthe method completes its execution. However, if the return type of an active objectmethod is a future, the method in the proxy class simply returns this same future tothe caller (and not a future to a future).

This mechanism allows the active object to return a future as its return value.This future is then simply passed on to the caller. In this case the caller has no way ofknowing when the method itself has been completed, as the value of the future may ormay not be known at that time. Later, when the value of the returned future becomesavailable to the active object, the KC++ run-time system automatically forwards thevalue to the caller.

Explicit return of futures is a very useful mechanism when it is combined withthe ability to explicitly create pending futures and later give them value using futuresources (which are explained in Section 5.5). In the buffer example the empty bufferobject might create and store a future source for each “unavailable” value it returnswith get, and return a pending future bound to that future source as a return value.Later, when put method is used to add a value to the buffer, it would bind the valueto the future source, automatically causing the run-time system to forward the valueto the caller of get. This kind of buffer is presented in Appendix C.

Another use for explicit future return types is to allow synchronisation to an othermethod in a different active object. If a method returning an explicit future returnsa future that is the return value of another method call, this future is forwarded tothe caller and the original method completes its execution. This results in a futurewhich receives its value when the other method (called by the original method) iscompleted. Listing 5.4 on the following page contains a piece of code which demon-strates forwarding of return values.

If the operation described in the previous paragraph is tried in a method whichdoes not have an explicit future return type, the situation is different. In that case theoriginal method waits for the called method to complete before it itself completes itsexecution. This blocks the active object from other calls, because there can only beone uncompleted method in each active object at any time. Listing 5.5 on page 67shows the code in Listing 5.4 without an explicit future return type. This makes

65

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Helper : public Active2 {3 public:4 int nextValue();

...5 };6

7 class Explicit : public Active8 {9 public:

10 Future<int> calculate();11 private:12 Helper h;13 };14

15 Future<int> Explicit::calculate()16 {17 // Do some other calculations18 return h.nextValue(); // Return immediately, no synchronisation19 }

LISTING 5.4: Use of explicit future return types

explicit future return types a convenient way of preventing methods from blockingactive objects unnecessarily.

Even when explicit future return types are used, the active object can still returna normal non-future value. There is an implicit type conversion from the underlyingtype of the future to the future type itself, so returning a non-future simply creates atemporary non-pending future object, and this future (with its value) is then forwar-ded to the caller, causing the caller’s return value future to receive its value when themethod completes.

5.3 Futures as parameters

Futures are useful for storing return values of asynchronous method calls and syn-chronising with them, but they have broader uses as well. They can be thought as“synchronisation tokens” which can be passed on to allow others to synchronise withselected events. This requires that futures may be passed between active objects asparameters. KC++ futures allow this as long as the underlying type is acceptable as anactive object method parameter.

Return values are not the only way to get information back from a method in C++.

66

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class NonExplicit : public Active2 {3 public:4 int calculate();5 private:6 Helper h;7 };8

9 int NonExplicit::calculate()10 {11 // Do some other calculations12 return h.nextValue(); // Synchronise first, then return value13 }

LISTING 5.5: Listing 5.4 with a non-future return type

They are sufficient only when there is a single value which has be returned. Althoughsometimes data structures like “pair” are used to return multiple return values, it ismuch more common to get additional return values through reference parameters.Although references are not usually accepted as KC++active object method parameters(see Section 4.2.6), future references are an exception, so that several return valuescan be returned.

5.3.1 Futures as value parameters

When a future is passed as a parameter to an active object method which does notexplicitly request a future parameter, the situation is simple: an implicit type conver-sion is used to convert the future to its underlying type, and this conversion waitsuntil the value becomes available. The value is then passed as a normal value para-meter.

However, if the method explicitly takes a future type as its parameter, the situationis more complex. The way parameter passing happens depends on whether the futurealready contains a value or whether it is still pending.

If the the value of the future is available, the parameter passing resembles thenon-future case described earlier. The method message contains simply a booleanflag indicating that the future is not pending and the value of the future. When acopy of the future is recreated on the active object side, this future also contains acopy of the value.

If the future is still pending when it is passed as a parameter, the method messageonly contains a boolean flag indicating a pending future and an ID field identifyingthe pending future. The copy of the future created on the active object side is alsopending, ready to receive the value from the caller.

67

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

Later, when the value becomes known to the caller, the KC++ run-time systemautomatically forwards the value to the active object, which stores the value in itslocal future. This way passing a pending future results in two messages to the activeobject — the actual method message and later a message containing the value of thefuture.

5.3.2 Futures as reference parameters

Reference parameters are normally used for three purposes in C++. Constant refer-ences are used as “in” parameters to pass read-only values to methods without copy-ing them. On the other hand, non-constant references are used for “in-out” paramet-ers, i.e. to pass a value to the method and later receiving a value back. Another use fornon-constant references is as “out” parameters, where the programmer does not actu-ally pass any value to the method, but is only interested in the value of the parameterafter the method completes.

Passing non-active parameters between active objects always involves copyingthe parameter values because each active object can reside in its own address space.Therefore it is not possible to achieve the effect of constant reference parameters,where the lack of copying is usually the only reason they are used. For this reasonKC++ does not allow constant future reference parameters in active object methods.

Non-constant future references can be used for either “in-out” or “out” parameters.The KC++ compiler has no way to know which use is meant, so it uses the “in-out”assumption which also includes the “out” case. When a future is passed as a referenceparameter to another active object, the code in the proxy object first inserts the currentvalue of the future (pending or not) into the method message, just like with normalfuture parameters. Next the proxy detaches the future from its old value and binds itto a new, pending value. The ID of this value is also included in the method message.

When the method is invoked in the active object, it creates a future with a valuethat was included in the method message. A reference to this future is then passed tothe actual method. When the method is completed, the value of the parameter future(either pending or not) is sent back to the caller in the same message as the returnvalue of the method. The ID received in the method message is used to indicate thatthe value should be bound to the future reference parameter.

This way the value of a future reference parameter becomes known at the sametime as the return value, meaning that reference parameters can be used to replacereturn values without changing the semantics of the program. It should be noted thatfuture reference parameters are similar to explicit future return types in the sensethat they can be used to forward pending futures received elsewhere in the program.

If a future reference parameter is used as an “out” parameter, the mechanism de-scribed above is usually quite appropriate. If the program creates a new future andthen uses it as a reference parameter to receive a value from a method, the value of thefuture in the method message is never used in the active object. A little more prob-

68

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

lematic situation occurs if a pending future is passed as an “out” parameter this way.In this case the eventual value of the parameter is actually unnecessary. However, theparameter passing mechanism sends the pending future to the active object, even ifits value is never used. When the value becomes known, the KC++ run-time systemforwards the value to the active object, even if the method has already completed.This is necessary because the run-time system cannot know whether the active ob-ject needs the value of the parameter or not.

A similar situation arises if a pending future is passed as a future reference para-meter, and the active object never uses the parameter. The KC++ run-time system inthe caller cannot know this, so it considers the value received from the method to bea different value than the old one. If the old value of the future is received only afterthe method has completed, the run-time system nevertheless sends the value to theactive object, whose run-time system simply binds this value to the “new” value ofthe parameter future and sends the value back to the caller. This “detour” cannot beavoided because the active object and the caller have no information on each other’sactions.

5.4 Pure synchronisation — void-futures

Although futures are normally used as place-holders for asynchronous method returnvalues, they have another important property — they allow callers to synchronisewith the completion of a method. Synchronisation in general is separate from returnvalue passing (although the act of receiving a return value always includes synchron-isation).

Despite being originally designed as place-holders for a value, futures can also beused for pure synchronisation. The C++ language has the type void, which is a “non-entity” in the sense that it represents a type which can contain nothing, i.e. an “emptytype” with no values. Variables of type void cannot be created in C++, but otherwisevoid is a completely normal type.

The KC++ systems allows the creation of futures with void as the underlying type.This future type, Future<void> is a specialisation of the normal future template. Itcontains all the necessary mechanisms for synchronisation, but no value. This meansthat void-futures have methods isready and wait, but not the method value or animplicit type conversion (the missing methods would be useless anyway, becausevalue would return void, and type conversion to void is practically an oxymoron).

Normally there is no way to synchronise with the completion of a method return-ing void. If such synchronisation is wanted, it is easy to implement with void-futures.The method can explicitly return a void-future, and the caller can wait using this fu-ture. The method itself can then create a non-pending void-future and return thatas its return value, making the void-future on the caller’s side also non-pending andreleasing the caller. Listing 5.6 on the following page shows an example object withthis kind of a method.

69

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Example : public Active2 {3 public:4 Future<void> voidSync();

...5 };6

7 Future<void> Example::voidSync()8 {9 // Here the actual code

10 return Future<void>(); // Return a non-pending void-future11 }12

13 void userfunc()14 {15 Example e;16 e.voidSync(); // No synchronisation17 e.voidSync().wait(); // Immediate synchronisation18 Future<void> sync = e.voidSync();19 while (!sync.isready()) { /* Do something while waiting */ }20 }

LISTING 5.6: Synchronisation with void-futures

A method returning a void-future can be called like a method returning nothing,like on line 16. In this case the call is asynchronous and the void-future return valueis discarded (the KC++ run-time system takes care of the eventual return value mes-sage). If immediate synchronisation is wanted, the caller can directly call wait on thereturned void-future, like on line 17. Like with normal futures, the third possibilityis to occasionally poll the void-future with isready and perform some other actionswhile waiting. This is shown on lines 18–19.

It would have been possible to design KC++ in a more consistent way and makeproxy methods return void-futures if the actual method returns void. This wouldhave made it possible to synchronise with any method. However, most of the timethe caller of a method returning nothing is not interested in synchronisation. Auto-matically returning a void-future would have meant that the active object would havenotified the caller also in these cases, adding overhead to the system. For this reason itwas decided that void-futures would be available only as explicit future return types,not automatically.

In addition to synchronisation with methods with no return value, void-futuresare very useful when combined with future sources explained in Section 5.5. Theycan be used as explicit synchronisation tokens which can be stored in containers,

70

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

passed between active objects etc. They also make KC++ simpler because futures canbe used as the only synchronisation mechanism in the whole system.

5.5 Creating futures explicitly — future sources

Return value futures and asynchronous method calls alone are not enough for concur-rent programming. If return value futures were the only mechanism for synchronisa-tion between active objects, all synchronisation would be restricted to the completionof some method. Explicit future return values allow this method to be different thanthe one the programmer calls directly, but still each future would receive its value atthe end of some method call.

The restriction comes from the fact that all active objects in KC++ execute theirmethods with one-method-run-to-completion semantics. If one method call is still inprogress, the execution of the other methods is started only after the current methodis completed. This makes it impossible to return a future from one method call andgive it a value as a result of another method call.

For example, it could be reasonable to implement the barrier synchronisationmechanism [Andrews, 1991, Ch. 3] as an active object. A barrier is a mutual syn-chronisation point for several execution threads. Each thread calls an operation “com-mit”, which blocks the execution of the thread. When a predetermined number ofthreads are waiting in the barrier, they are all released simultaneously. In KC++, thecommit-operation could return a void-future for synchronisation with the barrier.Listing 5.7 on the next page shows an outline of such a barrier class.

This barrier class cannot be implemented using only normal return value futures.The commit-operation has to return a future to the caller. If this future was createdin the method itself, the caller would proceed immediately after the method com-pletes. The method itself cannot wait for other participants, as the calls of the otherparticipants are blocked until the method completes. The only alternative would beto get the return value future from another active object, which would perform theactual barrier synchronisation. However, exactly the same limitations apply to thisactive object too, so it cannot create the future either. As a result, the barrier class isimpossible to implement this way.

The KC++’s solution the problem are future sources. Future sources are objectswhich can be used to explicitly create pending futures. Later the same future sourceobject is used to give a value to all futures which have been created from that particu-lar future source — or any future created from these futures. This way future sourcesseparate the actions of future creation and value binding from each other. They makeit possible to return a pending future from one method and later give it a value inanother method.

Like futures, future sources are a class template with the underlying type as a typeparameter. Futures with the same underlying type can be created from a future source

71

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Barrier : public Active2 {3 public:4 Barrier(int newcount);5 Future<void> commit();6 private:7 const unsigned int target count;8 unsigned int count;

...10 };11

12 Barrier::Barrier(int newcount)14 : target count(newcount), count(0) // . . .17 {18 }19

20 Future<void> Barrier::commit()21 {22 // Somehow create a void-future for synchronisation23 if (++count == target count)24 {25 // Release the future if enough participants27 }28 // Return the future for waiting & synchronisation29 return sync;30 }31

32 // An example object using the barrier33 class Process : public Active34 {35 public:36 Process(Barrier& newbarrier) : barrier(newbarrier) {}37 void run() {38 // Do something39 barrier.commit().wait(); // Synchronise with others40 // Continue41 }42 private:43 Barrier& barrier;44 };

LISTING 5.7: Outline of an active class implementing a barrier

72

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

object through an implicit type conversion. When a future source object is created,it is initially “empty,” and does not contain any value. All futures created from thiskind of empty future source are pending futures without any value either.

In addition to the type conversion for creating futures, future sources have amethod called bind. This method can be used to “fill” the future source and give it avalue. It creates an object of the underlying type, using its parameters as constructorparameters. After this, all futures which have been created from this future source arebound to this value. The KC++ future forwarding mechanism is used to send the valueto futures in other active objects, if necessary. The act of binding the future sourceto a given value is similar to giving the return value future its value at the end of anactive object method.

After the future source is given its value using bind, all subsequent futures createdfrom it contain this value and are not pending. In the current version of KC++ the bindmethod can take up to fifteen constructor parameters, as long as the underlying typehas a constructor which can accept the parameters. If necessary, it is very easy toextend this limit. Another way to overcome this limitation is to create an object withthe correct value outside the future source and then give this object as a parameter tobind, causing the future source’s value to be created using the copy constructor.

The barrier problem is now easily solved using future sources. Listing 5.8 on thefollowing page shows the complete barrier class. It contains a void-future source asa data member. This future source is used to create futures sent to the participants.Every time a new participant calls the commit method, counter is incremented. Ifthere are too few participants, a pending future is created from the future source andreturned to the participant on line 29. When the final participant calls commit, themethod first executes bind on the future source, releasing all participants waiting fortheir futures to get a value (or polling them). The underlying type of the future sourceis void, so there is no real “value” created here, but the synchronisation caused bybinding is still performed. Finally the method creates a non-pending future on line 29,so the final participant does not have to wait.

If all goes well in the example, the participants are finally released through the fu-tures. However, it is possible that the barrier object gets destroyed before enough par-ticipants have called commit. Although this probably should never happen, it couldbe possible because of exceptions etc. If a future source is destroyed before it is boundto a value, it is automatically bound during its destruction. There are no parameters tocreate the value from, so the default constructor of the underlying type is used. Thismakes sure that the destruction of a future source does not leave “stranded” pendingfutures.

Future sources can also be copied and assigned to each other. In this respect theyact like futures. When a future source is copied, both future source objects sharethe same “emptiness” (or value if the original future source is already bound), sothat futures created from either future source receive their value when bind is calledon either of the future sources. When a future source is assigned to another future

73

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Barrier : public Active2 {3 public:4 Barrier(int newcount);5 Future<void> commit();6 private:7 const unsigned int target count;8 unsigned int count;9 SFuture<void> sync;

10 };11

12 Barrier::Barrier(int newcount)16 : target count(newcount), count(0), sync()17 {18 }19

20 Future<void> Barrier::commit()21 {23 if (++count == target count)24 {25 // Release the future if enough participants26 sync.bind();27 }28 // Return the future for waiting & synchronisation29 return sync;30 }

LISTING 5.8: The barrier class using a future source

source, the target of the assignment first binds itself to the default value and thenstarts sharing the value of the other future source. In other words, assignment followsthe common destroy-then-copy semantics.

The barriers in Listing 5.8 can only be used once. However, the future source synccan easily be “reset” to non-bound state by assigning a new empty future source toit. Listing 5.9 on the next page shows an improved barrier class which resets itselfevery time enough participants have called commit. The barrier counts the number of“rounds” the barrier has made and returns this number as the return value of commit.If the barrier object is destroyed prematurely, the participants which have alreadycommitted themselves receive value zero (default value of unsigned integers) as around number.

In addition to resetting future sources, assignment and copying make it also pos-sible to store future sources in ISO C++ containers like vector or map. Appendix B,

74

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Barrier : public Active2 {3 public:4 Barrier(int newcount);5 Future<unsigned int> commit();6 private:7 const unsigned int target count;8 unsigned int count;9 unsigned int round;

10 SFuture<unsigned int> sync;11 };12

13 Barrier::Barrier(int newcount)14 : target count(newcount), count(0), round(1), sync()15 {16 }17

18 Future<unsigned int> Barrier::commit()19 {20 Future<unsigned int> retval(sync); // The return value future21 if (++count == target count)22 {23 sync.bind(round);24 ++round; // The next round25 sync = SFuture<unsigned int>(); // New “empty” future source26 }27 return retval;28 }

LISTING 5.9: A reusable barrier class

contains an example implementation of a priority semaphore as an active object. Thesemaphore uses a ISO C++ container priority queue to release waiters in priority or-der.

Each future source can be thought to represent a source for a single value whichbecomes known later in the program. Therefore it is important that each future sourceis bound to a value only once, i.e. that an already bound future source is not “re-bound.” This restriction becomes very important if future sources are copied andassigned, in which case a future source can become bound through another futuresource. The bind-once restriction cannot be verified at compile-time, so a dynamiccheck is necessary. If an attempt is made to bind an already bound future source, themethod bind throws an exception bad bind. Futures sources also provide a methodisbound which can be used to check the state of a future source.

75

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

5.6 Locking active objects

Methods of an active object do not necessarily provide the correct granularity forusing the object. Often a “service” provided by an active object consists of a seriesof method calls, and the caller may need to perform some processing between themethod calls. In many cases it is important that other threads do not access the objectbetween the method calls of the “service” or “transaction.” In other words, it must bepossible to lock objects for the duration of several method calls.

It is very important that each lock is eventually released. This becomes easily com-plicated when C++ exceptions are used, because exceptions create new possible flowsof control in the code, and the locks have to be released in all of them. A useful C++idiom for providing exception safe locking are guard objects, also known as “scopedlocking” [Schmidt, 1999]. A guard object is an object whose constructor acquires thenecessary lock and whose destructor releases the lock. When a guard object is createdin a scope, the lock is in effect after the creation of the object. When the scope ends,the guard is destroyed and the lock automatically released. This happens even if thescope is left because of an exception.

The KC++ system uses this idiom with a guard class ActiveLock. When an objectof this class is created, the constructor of the object sends a message to the activeobject it receives as a parameter, requesting it to grant a lock. If the active object isnot already locked, it locks itself and signals the lock object. If the active object isalready locked, the lock request is not processed until the old lock is released. Thelock requests enter the same method queue as other active object methods, so theyare serviced in FIFO order.

After the lock object constructor gets confirmation that the active object is lockedfor exclusive access, it completes and lets the thread continue its execution. After thisthe code can use the active object normally, without other users getting in the way.If the locker tries to lock the object again while it already holds a lock, the later lockrequest has no effect. However, the KC++ system keeps a count of these lock requests,so that the object is released only after the same number of release requests has beenissued.

When the program exits the scope of the lock object (either normally or becauseof an exception), the destructor of the lock object sends a release message to the act-ive object. The object then releases the object lock and continues to process methodrequests from any caller. If other users have called methods while the lock was ineffect, these method requests are stored in the method queue and are executed afterthe lock is released. These delayed requests can also include further lock requestsfrom other threads.

Lock objects provide a very natural way to limit the duration of locking to a certainportion of the code. Listing 5.10 on the following page shows a simple function whichuses object locking. Lock objects can also be used as data members of an object, inwhich case the active object is locked for the lifetime of the object containing the lockobject.

76

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 class Buffer : public Active2 {3 public:4 void put(int number);5 int get();6 bool empty() const;

...7 };

...8 int getIfPossible(Buffer& buffer)9 { // Get a number if buffer not empty, otherwise return 0

10 Future<int> number(0); // Return value if empty11 { // Create scope for locking12 ActiveLock lock(buffer); // Lock the buffer13 if (!buffer.empty()) { number = buffer.get(); }14 } // Closing the scope releases the lock15 return number; // Wait for the number and return it16 }17

18 void putTwo(Buffer& buffer, int i1, int i2)19 {20 ActiveLock lock(buffer); // Lock the buffer21 buffer.put(i1);22 buffer.put(i2); // Store the two number next to each other23 // Lock is automatically released24 }

LISTING 5.10: Object locking using lock objects

Lock objects can also be combined with standard C++ auto ptrs to safely lock anobject in one place and release it in another. In this case the lock object is createddynamically with new and then bound to an auto ptr which implements strict own-ership semantics [Kreft and Langer, 1998]. This auto ptr can then be passed as a“lock token” to another function etc. The auto ptrs pass the ownership of the lockobject to each other and make sure that the lock object is automatically destroyed(and the lock released) when the last auto ptr is destroyed. Listing 5.11 on the nextpage shows an example of this.

The class Active has also methods lockActive and unlockActive. These methodscan be used if explicit locking without lock objects is needed. This is normally notrecommended, because handling object locking and unlocking with separate methodscalls causes a deadlock if a user forgets to release an object lock. C++ is especiallyprone to these kinds of errors because of the exception handling mechanism. If an

77

CHAPTER 5. ASYNCHRONOUS METHOD CALLS AND FUTURES

1 auto ptr<ActiveLock> lockAndPutOne(Buffer& buffer)2 { // Lock object, put 1, then return the lock object3 auto ptr<ActiveLock> lockp(new ActiveLock(buffer));4 buffer.put(1);5 return lock;6 }7

8 void putOneTwo(Buffer& buffer)9 {

10 auto ptr<ActiveLock> lockp;11 lockp = lockAndPutOne(buffer); // Acquire the lock12 buffer.put(2);13 // lockp destroys the lock object automatically14 }

LISTING 5.11: Lock objects as lock tokens with auto_ptr

exception is thrown while an active object is locked, the program execution may skipthe code containing the lock release call. To prevent this the programmer would haveto design his error handling code carefully, taking into account every possible way alock might be left unreleased.

One use for lockActive and unlockActive methods is that they can be calledfrom other methods of the active object. This makes it possible to write methodswhich, once executed, lock the object exclusively for the same caller. Similarly an-other method could then release the lock at the end.

Object locks increase the possibility of deadlocks, even if lock objects are used.For this reason they should not be used unless locking is really necessary.

78

CHAPTER 6. IMPLEMENTATION OF KC++

Chapter 6

Implementation of KC++

This chapter discusses the current implementation of the KC++ system. The systemconsists of two parts: the KC++ compiler and the KC++ library. One of the design prin-ciples of KC++ has been to keep the compiler as simple as possible and implementmost things in the library. There are two benefits from this approach:

• The KC++ compiler is based on an existing C++ compiler front end. Keeping thecompiler as simple as possible makes it easier to change the compiler to use adifferent C++ compiler front end, if necessary.

• Debuggability has been another important design issue in KC++. This means thatthe source code produced by the compiler should be as close to the originalsource code as possible. If some feature can be implemented in the KC++ library,it means that the compiler does not have to alter the original source code toimplement that feature.

The first section in this chapter describes the KC++ compiler and the changes itmakes to the original source code. The second section explains the structure of theKC++ library and discusses some design issues related to the library implementation.

6.1 The KC++ compiler

The current KC++ compiler is implemented as a back end to an EDG C++ compilerfront end from Edison Design Group [EDG, 1999]. The KC++ syntax is exactly thesame as normal C++ syntax, so the compiler front end has not been modified in anyway. The interface between the EDG front end and the KC++back end has been kept assmall as possible, and it has been isolated into its own program module. This meansthat coupling between the front end and the back end is relatively low, making iteasier to implement a KC++ compiler using a different C++ front end, if necessary. Italso makes it possible to publish most of the KC++ compiler program code withoutviolating non-disclosure agreements with Edison Design Group.

79

CHAPTER 6. IMPLEMENTATION OF KC++

The compilation process is shown in Figure 6.1. During the compilation the com-piler front end first compiles the KC++ program into its own intermediate code, whichis represented as a graph-like data structure in the compiler’s memory. The C++ syn-tax checking is performed during this phase, so only those KC++ programs which aresyntactically correct C++ enter the second phase.

During the second phase the KC++ back end scans through the intermediate codeto find all code related to active objects. It then uses the original source files to createnew, modified C++ source files. These files are then compiled with a conventional C++compiler. The g++ compiler from the Gnu Compiler Collection (GCC) [GNU, 2000]has been used for this purpose, but any C++ compiler should do. The source gen-erated by the back end is standard-conforming and uses the KC++ library to achieveconcurrency. The compiler-generated source files are created in their own directorywith the same names as the original files.

KC++ source

C++ front end

intermediate code

KC++ back end

C++ sourcegenerated

C++ compiler(g++)

object file(s)

linker

executable(s)

KC++ library

KC++compiler

#include

FIGURE 6.1: The KC++ compilation process

80

CHAPTER 6. IMPLEMENTATION OF KC++

Finally the resulting object files are linked with the KC++ library, resulting in thefinal executable programs. The current KC++ system only creates one executable, butit is also possible to make a separate “server” executable for each active object. Thiscould be useful in a distributed environment, where each active object is run on adifferent computer.

The modifications to the original source code are kept at minimum. Using mod-ified original source code instead of generating the code from the intermediate codemakes the resulting code very close to the original. Because of this the source codegenerated by the KC++ compiler is completely human-readable and also mostly un-derstandable to programmers who are not familiar with the internals of KC++.

The high correlation between the original and generated source also helps in de-bugging KC++ programs using normal C++ debuggers. The line numbers in the gener-ated code are the same as in the original source, except for files containing definitionsof active classes, where an additional line has to be added to each definition, causingan offset of one line per active class. However, #line directives are added to necessaryplaces in the generated source so that debuggers and other code analysers still referto the correct lines in the original source code.

Each C++ source file generated from the original KC++ source has a #line directiveas its first line. This directive links the generated file with the original source file. Itinforms the actual C++ compiler to refer to the original source code in the debugginginformation it adds to the object files. For example, if the original file was “test.cc,”then the compiler-generated file “kcpp/test.cc” would start with the line

1 #line 1 ". ./test.cc"

The rest of this section describes briefly the changes made to the original KC++source when the compiler generates the resulting C++ source.

6.1.1 Active class definitions

When the KC++ compiler encounters a definition of an active class (i.e. a definition ofa class derived from Active), it creates the corresponding proxy class. This class isdefined in its own header file, created by the compiler. The compiler also creates asource file which contains definitions of the proxy’s member functions.

As explained in Section 3.2.3, proxy classes form an inheritance hierarchy similarto that of the active objects, with the base class Proxy on top of the hierarchy. Theproxy class contains future-returning versions of the methods of the active class. Thedefinitions of these methods create method messages, send them to the active object,and return appropriate futures. Constructors of the proxy class use the KC++ libraryto create a new thread of execution which then starts executing the method invokerof the new active object. Most of the functionality in the proxy class is written usingservices of the Proxy base class. This keeps the amount of generated code at minimumand helps to make the executable smaller.

81

CHAPTER 6. IMPLEMENTATION OF KC++

To be able to use the proxy class, the generated source file must also include itsdefinition. To achieve this, the compiler generates an #include statement before thedefinition of the active class. This statement reads in the generated proxy header file.The statement has to be on a line of its own, so an additional #line directive is addedafter it so that the following lines refer to the correct lines in the original source.Listing 6.1 on the next page shows an example of these directives. The inclusion ofthe proxy header is the only place in the generated source where the line numberingof the original file has to be modified.

When the definition of an active class is handled, the KC++ compiler also cre-ates a source file containing the code for the active object method invoker. This codesimply receives method messages, unmarshals the parameters and calls the appropri-ate methods of the active object. It is also responsible for creating and destroying theactual active object. The invoker is implemented as a static member function of theactive class, and the definition of the active class is modified to contain the declara-tion of the invoker.

6.1.2 Uses of active objects, pointers and references

All the code which uses active objects must be modified to use proxy objects in-stead. The KC++ compiler accomplishes this by scanning the intermediate code forany uses of active class names. It then replaces these with the names of appropriateproxy classes. In the previous example all occurrences of Testclass are replaced withProxy Testclass. There is one exception to this: the active class name is kept intactin the qualifications of the active class’s member function definitions and in the act-ive class definition itself. This means that the definition of member function foo ofTestclass would still begin with Testclass::foo. This is of course necessary becausethe member function definitions really are members of the active class, not the proxy.

In addition to this, all active object pointers and references have to be changedto instances of proxy pointer and reference templates for reasons explained in Sec-tion 4.3. In the example Testclass* is replaced with Ptr<Proxy Testclass> andTestclass& with Ref<Proxy Testclass> everywhere in the code.

The changes above do not affect the syntactical correctness of the program. Thepointer template defines all the same operations as a normal pointer, except for point-er arithmetic. However, arrays of active objects are not allowed, so pointer arithmeticis useless, anyway. Proxy classes define their own &-operator which returns a KC++pointer object, so expressions like

Proxy Testclass o; // Originally Testclass o;Ptr<Proxy Testclass> p = &o; // Originally Testclass* p = &o;

still compile. The pointer template class has a constructor and an assignment operatorwhich accept normal C++ pointers. These are used when a pointer object is initialisedor assigned from a new expression which has created a new proxy object (new of coursestill returns a normal C++ pointer).

82

CHAPTER 6. IMPLEMENTATION OF KC++

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . testclass.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4 class Testclass : public Active5 {6 public:7 Testclass* returnthis();8 int testmethod(double d);9 };

10 class Testclass2 : public Testclass11 {

...12 };

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . kcpp/testclass.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4 #include "Proxy-Testclass.h"5 #line 46 class Testclass : public Active7 {8 public:9 Ptr< Proxy Testclass > returnthis();

10 int testmethod(double d);11 };12 #include "Proxy-Testclass2.h"13 #line 1014 class Testclass2 : public Testclass15 {

...16 };

. . . . . . . . . . . . . . . . . . . . . . . . . . . . kcpp/Proxy-Testclass.h . . . . . . . . . . . . . . . . . . . . . . . . . . . .9 class Proxy Testclass :

10 public Proxy11 {16 public:17 Ptr< Proxy Testclass > returnthis();18 Future< int > testmethod(double);

...42 };

. . . . . . . . . . . . . . . . . . . . . . . . . . . kcpp/Proxy-Testclass2.h . . . . . . . . . . . . . . . . . . . . . . . . . . .9 class Proxy Testclass2 :

10 public Proxy Testclass11 {

...41 };

LISTING 6.1: Compiled active class definitions

83

CHAPTER 6. IMPLEMENTATION OF KC++

The KC++ library derives the reference template from its type parameter (the proxyclass), so it automatically contains all the public member functions of the proxy. Thederivation also makes it possible to use reference objects everywhere where proxyobjects are expected. The reference template class has a constructor which allowscreating a reference object from a proxy object, so proxy objects can also be used toinitialise a reference as in normal C++. Section 6.2.2 discusses the structure of pointerand reference templates in more detail.

6.1.3 Additional changes to pointers

There are a few more modifications needed to make pointer objects behave correctly.The KC++ back end scans the intermediate code for any places where the integer con-stant zero is used as an active object null pointer. These occurrences of zero are sur-rounded with an explicit type conversion, because implicit type conversions do notwork everywhere. This results in a code like

// Originally Testclass* p = 0;Ptr<Proxy Testclass> p = Ptr<Proxy Testclass>(0);int i = 0; // Not changed, not a null pointer constant

When a dynamically created proxy object is destroyed using delete, the code hasto be modified because delete needs a pointer, and the KC++ pointer object is not apointer. This is solved by replacing all expressions delete p (where p is a pointer toactive object) with p.destroy(), where the destroy member function of the pointertemplate class takes care of deleting the dynamically created proxy object.

Each object in C++ has an implicitly defined pointer this pointing to the objectitself. Because the pointer is implicitly defined, it remains a normal C++ pointer in anactive class. However, it too should be changed to a pointer object in the producedcode. This problem is solved by replacing each occurrence of this with a call to thepointer class’s static member function Ptrthis. This function creates a new proxyobject pointing to the active object itself and then returns a pointer object pointing tothis proxy. Listing 6.2 on the following page shows an example member function ofTestclass before and after the modification.

6.2 The KC++ library

The KC++ library contains all the KC++code which is not compiler-generated. As muchof the KC++ as possible has been implemented in the library, because making changesto the library is easier than changing the KC++ compiler and because keeping thecompiler small makes it more portable.

The library also contains all the functions which are responsible for creatingnew active objects (i.e. new threads of execution) and communicating between them.

84

CHAPTER 6. IMPLEMENTATION OF KC++

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . testclass.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .4 Testclass* Testclass::returnthis()5 {6 return this;7 }

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . kcpp/testclass.cc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5 Ptr< Proxy Testclass > Testclass::returnthis()6 {7 return Ptr< Proxy Testclass >::Ptrthis() /* this */ ;8 }

LISTING 6.2: Changes to the this pointer

This means that if someone wants to change these aspects of KC++ (to optimise KC++for shared-memory processors, for example), then only the KC++ library has to bechanged.

This section describes the overall structure of the main parts of the KC++ library:base classes Proxy and Active, active object pointer and reference template classes,future classes, and message streams.

6.2.1 Implementation of the base class Active

Each active objects in KC++ has to be derived from the base class Active. Mainly thisbase class acts as a “tag” which informs the KC++compiler that all classes derived fromit are active objects. The compiler then knows to write appropriate proxy classes andmodify the program code to use these proxies.

The base class Active is not merely a tag, however. It also contains methods com-mon to all active objects. At the moment this only means the methods lockActiveand unlockActive needed for explicit object locking. Active objects can also use themethod getlocker to ask for the ID of the process which has locked the object.

Internally the class contains information about the ID of the process which hasinvoked the current method, the ID of the process which has locked the object (ifany), and the current lock count (this is used to track the situation where the sameprocess “relocks” the object when it already holds a lock).

6.2.2 Implementation of proxies, pointers and references

Just as each active class is derived from Active (either directly or indirectly), eachcorresponding proxy class is directly or indirectly derived from the base class Proxy.This class defines most functionality needed in the proxy classes: method parameterhandling, return value future handling, proxy marshalling and unmarshalling, andobject locking.

85

CHAPTER 6. IMPLEMENTATION OF KC++

Internally each proxy object only needs to contain enough information to identifythe active object is represents. The type of this data is same for each proxy objectregardless of its actual class. Therefore the base class Proxy contains this informationand the derived proxy classes do not contain any data members of their own.

As the class Proxy provides all the necessary functionality for proxies, actualcompiler-generated proxy methods are very simple. They mainly call methods ofthe base class to create the method message, set up the return future and send themethod message to the active object. Listing 6.3 shows the implementation of theproxy method testmethod. First a method message is created and the method ID (astring in the current test implementation of KC++) is added to the message. Then avalue parameter and a return value future are added to the method message using ap-propriate helper methods. Finally the method message is sent and the method returnsthe future.

References to proxies are implemented as a simple template class in the KC++ lib-rary. Each reference is derived from the proxy class it refers to, which automaticallygives the reference object the same public interface as in the actual proxy. This deriv-ation is implemented by defining the reference template in the following way:

template<typename T>class Ref : public T{

...};

The only difference between a proxy reference object and a real proxy object is thatthe public constructors of the real proxy class create a new active object, whereasthe constructors of the reference class simply bind the reference to an existing activeobject.

Pointers to proxies are also implemented as a template class. Because most of thefunctionality in different pointer types is exactly the same, the KC++ library defines

1 Future< int > Proxy Testclass::testmethod(double p1)2 {3 ObjectOMsg omsg;4 setupMethod(omsg, "testmethod:int(double)");5 add valpar(omsg, p1);6 Future< int > retval( add retval< int >(omsg));7 omsg.send();8 return retval;9 }

LISTING 6.3: A compiler-generated proxy method

86

CHAPTER 6. IMPLEMENTATION OF KC++

a common base class for all proxy pointer types. This class contains all the com-mon code, which is thus automatically shared between different proxy pointers. Theactual pointer template implements only the type-specific part of the interface. Thecommon base class also makes it easier to implement pointers in a way which makesit possible to assign a derived class proxy pointer to a base class proxy pointer, forexample. However, the details of the pointer implementation are beyond the scope ofthis thesis.

Active objects (proxies) and pointers and references to them can be used as activeobject method return values. Because all method calls are asynchronous, the proxy,pointer and reference classes have to provide future-like functionality. This is em-bedded inside the classes, so that proxy objects, as well as pointer and referenceobjects, can also be “pending,” i.e. not yet bound to any active object. This state ishidden from the user, and if the proxy, pointer or reference object is used before ithas its value, the program execution is simply paused until the value becomes avail-able. The implementation of proxies, pointers and references as futures also makes itpossible to share quite a lot of their code with futures, reducing the size of the KC++library.

6.2.3 Implementation of futures

The KC++ futures are implemented completely as a library template class and do notrequire any support from the KC++ compiler. As with proxies and proxy pointers,all KC++ future types have a common base class which contains practically all theoperations. This base class is not a template, so all futures share the same code, andintroducing new future types to the program does not significantly increase the sizeof the program. Future sources are also implemented using the same class hierarchy,so they also share much of the same code.

Normal futures

Several future objects may share the same (possibly pending) value. This means thatthe value itself cannot be directly stored in a data member of a future, because itwould then be automatically destroyed when this particular future is destroyed. Toallow sharing the KC++ futures use the “pimpl” [Sutter, 2000] idiom and store allthe necessary data in a private class. Each future object only contains a pointer tothis private object, which contains the actual value of the future and other necessarysynchronisation information.

This “value object” has a reference counter, which is incremented each time anew future starts sharing the value with an old one. Correspondingly the counter isdecremented when a future object is destroyed and thus detached from the structure.This implementation makes futures very light-weight. Copying and assigning a futureonly requires updating the reference counts. This method is usually known as the

87

CHAPTER 6. IMPLEMENTATION OF KC++

“counted handle/body” idiom [Coplien, 1992] (or [Coplien, 2000]).

The internal class has to be different for each future type instantiated from the fu-ture template, because the type of the value the future contains is different. However,all other parts of the value objects are identical. The KC++ futures utilise this fact byusing the “Rungs of a Dual Hierarchy” pattern [Martin, 1997]. The mutual base classof futures defines a “value base class.” This class only contains the reference countand synchronisation information.

The future template then derives a new template class from this value base class.This derived template class has an additional data member of the correct type tocontain the actual value of the future. Figure 6.2 shows the structure of the KC++future implementation. It has been somewhat simplified to make the figure clearer.

The void-future class is a template specialisation of the actual future template,because it differs from other futures. The class Future<void> does not provide anymember functions for accessing its value because it does not contain any value. Sim-ilarly the “value class” of the void-future is a specialisation of the general value classtemplate, and contains no actual value member.

The value base class also defines pure virtual functions for marshalling and un-marshalling. The derived class template then defines these functions appropriatelyso that they marshal and unmarshal the future value. This allows the code in the fu-ture base class to perform marshalling and unmarshalling without knowing the typeof the future. Because of this the future classes themselves do not have any virtualfunctions. This makes the future objects even more light-weight, because they do nothave to contain virtual table pointers and other overhead related to dynamic binding.

Future sources

Future sources are implemented by deriving the future source template from the nor-mal future template using private inheritance. The use of private inheritance is ap-propriate here, because future sources “are implemented in terms of” [Meyers, 1998,

FutureBase

FutureFutureT void

ValueBase

ValueT value

ValueT void

(template specialisations)

FIGURE 6.2: The KC++ future implementation

88

CHAPTER 6. IMPLEMENTATION OF KC++

Item 42] normal futures. Future sources could not have been implemented using ag-gregation, because they have to have access to the protected member functions of thefuture class.

Deriving future sources from futures makes their implementation easy. For eachfuture source there is now automatically a local future value object (in the futureclass). When new normal futures are created from the future source, they simplystart sharing the same value object. When the future source is bound to a value, itstores the value in the value object. This also makes the value available to all normalfutures sharing the same value object. Furthermore, the future value forwarding sys-tem (explained in the next section) then automatically sends the value to necessaryother active objects.

Several future sources may share the same pending value. Therefore the futurevalue class also maintains a reference count of future sources sharing the same valueobject. This reference count is used to notice if all future sources are destroyedwithout binding any value. If this happens, the last future source to detach itself fromthe value object automatically binds the object to the default value of the underlyingtype.

Forwarding future values

The future value forwarding mechanism is a very important part of KC++ because,in addition to parameter passing in method messages, it is the only mechanism forsending data from one active object to another. There are two sides to future for-warding: the sender side and the receiver side. Both sides have to know about thesituation when a future is pending — the sender has to send the value to the correctdestination, and the receiver has to know where to store the received value.

Each future in the whole system has a unique future ID. This ID identifies theactive object where the future resides as well as the future itself inside the activeobject. These IDs are used as “addresses” or “tags” when sending future values.

On the receiver side, a future may become pending in three different ways. First,it may have been created locally from an empty future source. In this case it sharesthe value object with the future source object and will automatically receive the valuewhen the future source is bound. Secondly, it may have been created as a return valuein a proxy method. The ID of this future has then been sent in the method messageto the active object. Thirdly, the future may have achieved its pending state whena received future (a method parameter or a part of an unmarshalled data structureor non-active object) is unmarshalled into it. In this case the unmarshalled messagecontains the ID of the pending future on the other side.

In both latter cases the KC++ run-time system has to keep track of pending futuresso that when a future value message is received, the value is unmarshalled into thecorrect future. Section 5.1.6 explained that the KC++ run-time system maintains a list

89

CHAPTER 6. IMPLEMENTATION OF KC++

of all pending futures to prevent their premature destruction. This list is actually amap data structure linking future IDs to local future objects.

Each time a pending future is created and its value will be received from outside,the run-time system inserts the ID of the future and a future object sharing the valuewith the created future to the map. When a future value message is received, the IDof the value is extracted from the message and used to find the correct future objectin the map. Then the run-time system binds the value to the future and removes theID and the future from the map.

On the sender side, the need to send a future’s value elsewhere may arise fortwo reasons. The value may be the return value of a method or the value of a futurereference parameter, which must be sent to the caller. Alternatively a pending future(i.e. its ID) may have been sent to another active object, either as an explicit futurereturn value or as a part of a non-active data structure or object.

The value to be sent may become available for two reasons: either a future sourceis bound to a value, or a local pending future receives its value from outside, and thisvalue must be resent to other active objects. Future sources are internally implemen-ted as futures, so both these cases are actually the same: a local pending future hasreceived its value, and this value must be sent elsewhere.

The KC++ run-time system keeps also a map of all pending futures whose even-tual value must be forwarded elsewhere. When a local future receives its value (forwhatever reason), the run-time system checks from the map whether the value of thefuture must be forwarded. The map also contains the destination of the value. Therun-time system creates appropriate future value messages, sends them, and then re-moves the entries from the map, thus forwarding the value to all appropriate activeobjects.

It should be noted that both the receive and send mechanisms may be triggered atthe same time if a pending future has already been sent elsewhere. For example, anactive object could receive a pending future as a method return value and then sendit to a third active object as a future parameter. When the first method call completes,the future’s value is sent to the caller. There the run-time system first binds the valueto the local return value future and then immediately forwards the value to the thirdactive object.

6.2.4 Implementation of message streams

The message streams in the KC++ library are responsible for conveying informationfrom one active object to another. The programmer only has to care about messagestreams if he wants to pass his own non-active types as active object method para-meters. In this case he has to write the appropriate marshalling and unmarshallingfunctions for his type. The KC++ library itself contains all necessary marshalling andunmarshalling functions for all the C++ built-in types, as well as for futures, activeobjects, and active object pointers and references.

90

CHAPTER 6. IMPLEMENTATION OF KC++

There are two types of message objects: output streams and input streams. Aninput message stream (IMsg) represents a message received from outside. It containsinformation about the sender and the type of the message (either a method messageor a future value message). The KC++ library uses these to decide how to handle themessage, and then uses unmarshalling functions to read the method ID and parametervalues (if the message is a method message) or the future value (if the message is afuture value message).

Similarly output message streams (OMsg) represent messages to be sent elsewhere.Each message object contains the address of the message’s recipient and the typeof the message (again either a method or a future value message). Method messagesare created by the proxy objects when any of their methods are called. Future valuemessage are created in two places. The active object method invoker creates them tosend the return value (or values) when a method has been completed. The KC++ run-time system creates them to forward future values to other active objects. Marshallingfunctions are used to put necessary parameter values or future values to the messagebefore sending. The output message class has a method send() which takes care ofsending the message to its recipient.

It should be noted that even though the names of the classes refer to “messages,”the actual communication do not necessarily have to use a message passing system.If all active objects have a shared memory, it is possible to write the message objectsto utilise this and “send” the information using semaphores and shared memory. Itis even possible (and planned, see Chapter 8) to write a version of the library wheresome objects may have shared memory while others do not. The library would thenuse message passing between two active objects which do not have shared memory,and the more efficient shared memory approach otherwise.

6.2.5 Implementation of unmarshalling

Marshalling into an output stream always happens with an overloaded operator <<.However, there are two possibilities for unmarshalling, as explained in Chapter 4.Normally an unmarshalling constructor should be used, but the programmer can alsoprovide an unmarshalling operator >> and declare it with a special macro calledSPECIAL UNMARSHAL. This section explains the reasons for two unmarshalling mech-anisms and describes the implementation of SPECIAL UNMARSHAL.

Semantically the marshalling/unmarshalling sequence is very close to creating acopy of an object. The only differences are that the copy is possibly created in anotheraddress space and that the creation of the actual copy may be delayed (the unmar-shalling does not have to happen immediately after marshalling). Copying objectsis performed in C++ with copy constructors, so it was considered sensible to makeunmarshalling as close to copy construction as possible. The logical choice was to re-quire that unmarshalling is performed in an unmarshalling constructor, which is justlike a copy constructor except that it takes an input message object as a parameter.

91

CHAPTER 6. IMPLEMENTATION OF KC++

In many other systems (CORBA, CHAOS++, and UC++, to name but a few) un-marshalling is only possible with an unmarshalling function which unmarshals areceived message into an already existing object. There are two important semanticaldifferences to copying in this approach. First, unmarshalling into an existing objectcorresponds to assignment, not copying. These two things can be quite different inC++ with some user-defined classes. Secondly, the object to which the message is un-marshalled must be created somehow. Usually the default constructor is used for thispurpose, as the part of the library doing the unmarshalling does not know anythingabout the object to be created.

However, this means that the classes to be unmarshalled must provide defaultconstructors, even if there is no other need for them. It is widely agreed in the C++community that unnecessary default constructors should be avoided, because theyhide some common programming mistakes [Meyers, 1996]. Furthermore, in manycases it may be impossible to write a default constructor which would result in anobject which is in a “full working order”. In these cases the programmer has to some-how mark the object as “incomplete” in the default constructor, and then changethis state after unmarshalling is complete. For these reasons the unmarshalling con-structor was considered a better choice, even though it is not symmetrical with themarshalling function.

The unmarshalling constructor has its limitations, though. It is not possible towrite new constructors for built-in types or enumeration types. Similarly it is oftenimpossible to add new constructors to third-party classes. For these cases, the unmar-shalling operator >> is practically the only choice. However, the code in the KC++library cannot automatically know whether to use the constructor or the >> operatorfor an arbitrary type.

The problem is solved in KC++ with template specialisation, which makes it pos-sible to treat some types in a different way. The KC++ library code performs unmar-shalling using an inline template function called KCPP unmarshal, whose definition isshown in Listing 6.4 on the next page. The function takes two parameters. The firstparameter is a pointer to an uninitialised area of memory where the unmarshalledobject is to be created (using placement new). The second parameter is a referenceto the input message object to unmarshal from. The default version of this functionsimply uses the type’s unmarshalling constructor to unmarshal and create the object.

If the unmarshalling constructor is not an option for a certain type, a templatespecialisation of KCPP unmarshal can be defined for this type. This specialised func-tion creates the object using its default constructor, and then uses operator >> tounmarshal the data to the object. The KC++ library makes this specialisation easy byproviding macro SPECIAL UNMARSHAL, which takes the type as a parameter and definesthe necessary template specialisation. The library already contains specialisations forall built-in types, and the users can easily declare their own specialisations for typeswhere the unmarshalling constructor is impossible to implement.

92

CHAPTER 6. IMPLEMENTATION OF KC++

1 template<typename Type>2 inline void KCPP unmarshal(void* position, IMsg& imsg)3 {4 return new(position) Type(imsg);5 }6

7 #define SPECIAL UNMARSHAL(Type) \

8 template<> \

9 inline void KCPP unmarshal<Type>(void* position, IMsg& imsg) \

10 { \

11 Type* p(new(position) Type); \

12 try \

13 { imsg >> *p; } \

14 catch (. . .) \

15 { p−>~Type(); throw; } \

16 }LISTING 6.4: The definition of SPECIAL_UNMARSHAL

93

CHAPTER 7. RELATED WORK

Chapter 7

Related work

This chapter discusses some other work related to KC++. It does not attempt to be acomplete survey, but it contains some information that was found during the creationof KC++ and writing of this thesis.

7.1 Classification of concurrent object-oriented systems

There are many different ways to classify and categorise concurrent object-orientedsystems. One such classification is presented by Briot, Guerraoui, and Lohr in “Con-currency and Distribution in Object-Oriented Programming” [Briot et al., 1998]. In thearticle, different approaches to the problem of adding concurrency to object-orientedprogramming are divided into three categories: the library approach, the integrativeapproach, and the reflective approach.

In the library approach, concurrency is introduced in the form of a library (usuallya class library) which provides necessary services for asynchronous calls, mutualexclusion etc. This approach is briefly discussed in Section 7.3. Although KC++ alsocontains a library, the it does not belong to this category, because concurrency supportis added to the program by the KC++ compiler.

The integrative approach adds concurrency by unifying some concurrent conceptswith object-oriented ones, thus “mapping” concurrency into object-oriented program-ming. KC++ is an example of this approach, as the concept of “active objects” is simplyan unification of the concepts of “thread of execution” and “object”. The article ofBriot et al. mentions that this unification is problematic, because there are easilysome conflicts with different concepts. Inheritance anomaly [Matsuoka and Yonez-awa, 1993] is mentioned as one example. It means a case where the synchronisationconstraints of the base active class must be partially rewritten in a derived activeclass. This anomaly does not affect KC++, however, because KC++does not provide syn-chronisation constraints (the one-thread-run-to-completion semantics makes themunnecessary).

94

CHAPTER 7. RELATED WORK

The reflective approach is based on metaobject protocols [Kiczales et al., 1991]and metaprograms. The idea is “to separate the application program from the vari-ous aspects of its implementation and computation contexts (models of computation,communication, distribution, etc.).” [Briot et al., 1998]. The reflective approach isprobably the most “general” and flexible of these approaches, but it is also quite anew concept, so there are still few concrete systems which would be purely based onthis approach.

7.2 Concurrent object-oriented languages

Some object-oriented languages have concurrent features built in. This section men-tions some common languages and briefly discusses their support for concurrentobject-oriented programming.

The Ada language has had support for concurrency from the beginning, and sodoes the current Ada-95 [Ada, 1995]. The Ada language is not really object-oriented,but object-based like C++. For this reason concurrency support in Ada is not on objectlevel, but the language contains non-object-oriented mechanisms for task creationand synchronisation (“rendez-vous”). These mechanisms are enough to provide sup-port for concurrent programming.

The Smalltalk language has also had basic support for concurrency since Smalltalk-80 [Goldberg and Robson, 1983]. Since Smalltalk is completely object-oriented, prim-itives for concurrency are also based on classes. A Process class represents actionsthat can be executed concurrently, and class ProcessorScheduler takes care of actu-ally scheduling the processes. Synchronisation is handled with the class Semaphore,which implements traditional semaphores. The Smalltalk library also provides someother classes based on Semaphore to make concurrent programming easier. However,all method calls are still synchronous, so asynchronous behaviour must be codedexplicitly. There are also extensions to Smalltalk concurrency, like ConcurrentSmall-talk [Yokote and Tokoro, 1987].

The Java language [Arnold and Gosling, 1998] has also built in concurrency sup-port. Java is widely used in Internet programming, where reactive programming (inwhich the program must react to several simultaneous events) is often needed [Lea,1997]. As a programming language Java is almost completely object-oriented, so it isnot surprising that concurrency support is also built using classes and objects.

The class java.lang.Thread is used to initiate and control new activities. Eachinstance of this class represents a new thread of execution. Keywords synchronizedand volatile are used to control how methods are executed concurrently. The Java“base” class java.lang.Object has also methods wait, notify, and notifyAll, whichcan be used for synchronisation.

Concurrency in Java is based solely on creating new instances of the thread class.As in Smalltalk, all method calls are still synchronous (including the Remote MethodInvocation, RMI).

95

CHAPTER 7. RELATED WORK

7.3 Concurrent object-oriented libraries

In the library-based approach the underlying object-oriented programming languageis not changed at all, but all services related to concurrency are provided in theform of a class library. The library approach is quite useful in very flexible languageslike Smalltalk, where several concurrency libraries like Simtalk [Bezivin, 1987] andActalk [Briot, 1989] have been built on the relative basic concurrency support of themain language.

In less flexible languages like C++ the library approach does not easily provide as“natural” results, because the language has fixed semantics for object-oriented fea-tures such as method invocation. However, C++ is such a widely used language thatseveral concurrency libraries have been written for it.

Many such libraries provide a class called “Thread” or “Process”, which can beinstantiated to get a concurrently running thread of execution. Usually the program-mer is supposed to derive his own classes from this class. The concurrently executedcode is written either in the new class’s constructor, like in Sun’s C++ Coroutine Lib-rary [Sun, 1996], or in a specialised virtual function.

Active objects are also possible in the library approach. ABC++ [O’Farrell et al.,1996] is implemented as a class library, and has an inheritance-based active objectconcept like KC++. Active object creation and asynchronous method invocation hasto be performed using a special syntax and helper functions, because ABC++ does nottouch the source code in any way.

One quite specialised approach is to provide a library for parallel handling of acollection of similar objects, aimed especially for high-performance parallel comput-ing. One such library is the Amelia Vector Template Library (AVTL) [Sheffler, 1996].AVTL provides containers which resemble the standard C++ containers like vector,and a possibility to perform parallel operations on the elements of containers. AVTLalso provides generic function objects which can be used to act on the elements ofcontainers in a parallel manner. The library is designed for distributed-memory en-vironments, so it also takes care of necessary marshalling and unmarshalling betweenexecution locations. The pC++ system [Yang et al., 1996] is also a “collection-based”system like AVTL, but it also provides a small extension to the C++ syntax on top of itsclass library.

Frameworks are becoming more and more popular in object-oriented program-ming. It is therefore not surprising that they have also been used to add concurrencyto object-oriented programming. POOMA (Parallel Object-Oriented Methods and Ap-plications framework) [Reynders et al., 1996] is such a framework for data-paralleland scientific applications. The programmer inherits his own classes from the frame-work and uses services provided by it. The framework itself performs necessary datadecomposition and communications.

Several existing C-based concurrent libraries have also been ported to C++. Theseinclude MPI++ [Skjellum et al., 1996] based on the MPI library, and CHAOS++ [Chang

96

CHAPTER 7. RELATED WORK

et al., 1996] based on the CHAOS library. Both add some object-oriented support tothe original libraries.

There are many C++ libraries which provide traditional building blocks for con-currency, like semaphores, mutex locks, monitors, remote function calls, etc. Suchlibraries include the ACE library [Schmidt and Wang, 1995] and Threads.h++ [RogueWave, 2000].

Finally, Object Management Group’s CORBA [OMG, 1998a] has also support forconcurrency. CORBA is mainly aimed for distributed object-oriented programming,but it also provides mechanisms for asynchronous processing through dynamic invoc-ation interface. The new CORBA Messaging Specification [OMG, 1998b] provides alsoAsynchronous Method Invocation (AMI), which makes asynchronous method callseasier for the programmer. It contains also future-like mechanisms for delayed returnvalues.

7.4 Concurrent extensions to C++

Just like there are many concurrent object-oriented libraries available for C++, thereare at least as many concurrent extensions to the language. Many such extensions arepresented in “Parallel Programming Using C++” [Wilson and Lu, 1996]. This sectiondescribes the main features of a few such extensions, and briefly compares them withKC++.

The CC++ language [Kesselman, 1996] is a superset of C++. It adds concurrency toC++ with six new keywords. With par, parfor, and spawn programs can create parallelcode blocks, parallel loops, and asynchronous function calls. Keywords atomic, sync,and global are used for atomic operations, synchronisation, and shared data.

The new keywords in CC++ are not object-oriented as such, but can of course beused in connection with objects. This gives the programmer a possibility to write con-current programs in non-object-oriented manner. On the other hand, if active objects,futures etc. are needed, they have to be written by the programmer himself using theconcurrency primitives in CC++. An article about CC++ is also found in “Research Dir-ections in Concurrent Object-Oriented Programming” [Chandy and Kesselman, 1993].

The CHARM++ language [Kale and Krishnan, 1996] is based on the concepts of itspredecessor, the CHARM language, which is an extension to C. CHARM++ also imple-ments active objects where only one method may be active at any time. The languageadds several new keywords to C++ to mark and create chares (CHARM++’s name foractive objects), external interfaces and message passing. All chares are created dy-namically, with a syntax similar to C++ new. Data can be shared between chares usingseveral different “shared object types”. CHARM++ also provides support for dynamicload balancing between physical processors, etc.

Unlike the two languages described previously, the COOL language [Chandra etal., 1996] is designed for shared-memory multiprocessor only. This makes the lan-

97

CHAPTER 7. RELATED WORK

guage simpler, because communication can be performed through shared memorylocations. Concurrency in COOL is implemented with parallel functions, and Hoaremonitors and condition variables are used for synchronisation. Parallel functions canalso be invoked “serially”, if necessary. Return values from parallel functions are nor-mal return values, not futures, so if synchronisation to the completion of a functionor method is needed, it must be expressed explicitly in the program (using conditionvariables).

Some extensions aimed for high-performance computing concentrate on provid-ing means for operating on elements of a collection in parallel. Section 7.3 alreadymentioned some class libraries for such purposes. The C** language [Larus et al.,1996] is a data-parallel extension to C++ with similar features. It extends the C++ syn-tax with constructs to create matrices etc., and to perform user-defined operationson them in parallel. C** also provides tools for combining the results from paralleloperations and other tools needed in high-performance computing.

Like C**, the Mentat system [Grimshaw et al., 1996] is aimed for high-performancecomputing. In Mentat, the programmer marks classes with the keyword mentat if theyrequire parallel execution. The Mentat run-time system then creates data-dependencygraphs, which model dependencies in the program. The run-time system managesexecution of objects based on the dependency graphs. Mentat also provides a return-to-future construct which closely resembles futures in KC++, but is restricted to beused for returning values from parallel functions.

The MPC++ language [Ishikawa et al., 1996] takes a somewhat different approach.It extends the syntax of C++ with a few new keywords, which add multi-threadingand message-driven execution. However, MPC++ also provides a modification facil-ity called the MPC++ Meta-object Protocol (MOP). This protocol is similar to thatdescribed in [Kiczales et al., 1991]. With the protocol programs can introduce newsyntactic constructs and redefine existing ones in order to implement new parallelprogramming constructs.

In many ways, the C++// language [Caromel et al., 1996] is similar to KC++. C++// alsouses the existing C++ syntax and uses inheritance to mark classes as active. The C++//compiler also converts C++// code to normal C++ code, and uses wait-by-necessities (aform of future) to synchronise with asynchronous method calls. However, all activeobjects in C++// have to be created dynamically (requiring special care when excep-tions can occur). The wait-by-necessities are not parametrised in C++//, but rather theyare created using inheritance. This means that wait-by-necessities cannot be createdfor built-in types.

Finally, there is the predecessor of KC++, the UC++ language [Winder et al., 1996].Some features of KC++ come directly from UC++. For example, KC++ active objectsuse proxy objects and the same one-thread-run-to-completion semantics as UC++ act-ive objects. However, UC++ does not use type system to mark objects as active. Anyobject can be made active in UC++ by creating it dynamically with a new operatoractivenew. Again, this means the programmer has to be careful with exceptions. Nor-

98

CHAPTER 7. RELATED WORK

mal C++ method calls are always synchronous in UC++. If asynchronous method callis needed, the language provides an alternative syntax to achieve this. Future typesare not used, so asynchronous calls do not have return values. UC++ proxy classes areimplemented as special cases of the active classes themselves, making them heavy-weight, as they contain all data members of the active classes themselves.

99

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

Chapter 8

Conclusions and future work

This thesis has presented KC++, a concurrent programming system based on the C++language and using active objects for concurrency. It adds concurrency to C++withoutextending the language syntax. The KC++ system is not restricted to environmentswith shared memory, but each active object may have its own private address space.This includes distributed computing where active objects resides in different ma-chines and communicate with each other using a computer network. Future types areused extensively in KC++ to provide asynchronous method calls and synchronisationbetween active objects.

8.1 Advantages of KC++

The KC++ system in its current state has shown that it is possible to add active ob-jects and concurrency to the C++ language in a way that feels “natural,” i.e. in a waythat does not unnecessarily restrict the use of other language features or commonlyused C++ programming idioms. This “non-restrictiveness” is quite important in a lan-guage like C++, because one of the design goals of the language itself has been beento make different language features non-conflicting and “orthogonal”, leaving it up tothe programmer to decide how to use the features [Stroustrup, 1994, p. 113].

8.1.1 Familiar syntax and “feel”

The familiar syntax makes it easier to start using KC++, as the programmer does nothave to learn any new peculiarities added to the already complex C++ syntax. Ofcourse KC++ programmers still have to have good knowledge on concurrent program-ming, because KC++ does not give complete protection from usual concurrent pro-gramming mistakes such as dead-locks.

Active objects in KC++ can be handled exactly the same way as normal objects.They can be created dynamically or statically, and pointers and references to active

100

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

objects behave in the usual way. This is important because many C++ programmingidioms and practises often rely on the way objects are created, passed as parametersetc. These include design patterns, exception-safe programming, generic program-ming, and many others.

8.1.2 Making old classes active

KC++ makes it possible to take an existing class and derive an active class from it,allowing programmers to use already existing class libraries concurrently. This fea-ture of KC++ should be used with utmost care, however, as the interface of manynon-concurrent classes simply isn’t suitable for concurrent use.

8.1.3 Ease of debugging

The C++ code produced by the KC++ compiler is almost identical to the original sourcecode. This is a benefit when KC++ programs are tested and debugged. Standard C++debuggers can be used to debug KC++ programs interactively and step through theprogram line by line. The only restriction is that most debuggers are probably onlyable to debug one execution thread at a time (although there are debuggers which canhandle several concurrent threads at once). When a problem is found and located, the#line directives and the similarity between the produced C++ code and the originalKC++program makes it easy to find the location in the original program. The producedC++ code is human-readable, so it can also be used as a base of debugging, if theprogrammer wants to step through and inspect the produced code instead of theoriginal KC++ source.

8.1.4 Platform independence

The code produced by the KC++ compiler is standard C++ without any platform-specific extensions or dependencies. All such platform-specific features are imple-mented in the KC++ library. This makes it quite easy to port KC++ to other platforms. Italso means that if multiple platforms are needed, the KC++ program can be compiledonly once, and the resulting C++ code can then be compiled for each platform usinga native C++ compiler, and linked with the KC++ library for that platform.

As much of the KC++ library as possible has been made platform-independent.Proxies and futures are implemented using a simple stream-based communicationssubsystem, which can be implemented to use message passing (using either con-nectionless or connection-based communications), shared memory, or other mech-anisms. If several such subsystems are implemented, changing the communicationsmechanism only requires the programmer to link the KC++ program with the chosensubsystem.

101

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

8.1.5 Only one communication and synchronisation mechanism

Futures are used throughout the KC++ system for delayed value passing and syn-chronisation. In addition to asynchronous method calls, programmers can use futuresources to use futures explicitly for more complex synchronisation techniques. Usinga single synchronisation mechanism makes KC++ simpler and easier to use.

8.2 Disadvantages of KC++

Like any other system (including C++ itself), KC++ has its own disadvantages. Someof them are dictated by the C++ language, some are conscious design decisions, andsome are simply things which are not yet fixed in the current version of KC++. Thissection mentions some of those disadvantages and gives a short analysis of each ofthem. Some of the disadvantages are planned to be fixed in future versions of KC++,in which case the future plans are not discussed in this section, but are saved toSection 8.3.

8.2.1 The active object model and RTC

Maybe the most important “disadvantage” comes from the active object model usedin KC++. As mentioned in Chapter 1, there are several ways to add concurrency to anobject-oriented programming language, and different people have different opinionson the best way. The one-method-run-to-completion semantics (RTC) used in KC++ isperhaps the most restrictive way to implement active objects. Some people may con-sider it even too restrictive, because an active object method cannot “pause” and giveway to other methods, but its code must be run to completion before other methodscan be executed. In some cases it would also be useful to have several concurrentlyrunning methods in an active object (for example in a buffer where only one writerbut several readers are allowed).

There are two main reasons why RTC was chosen. It was already used in UC++,which was the “mother” of KC++. Therefore the whole issue was not even consideredduring the early stages. RTC is also the easiest and most efficient to implement. Wheneach active object consists of a single non-interruptible thread of execution, each act-ive object can simply be implemented as an operating system process or thread. Thereare also no mutual exclusion problems with the internal data of the active object. Theone-to-one relationship between an active object and an execution thread also forcesthe programmer to explicitly decide how many concurrent processes the program has(or to write code which makes this decision). This follows the C++ ideology where “theprogrammer is allowed to decide.” [Stroustrup, 1994]

102

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

8.2.2 Active object reference counting

One real problem in current KC++ is its way to determine lifetimes of active objects.The current reference counting system means that an active object remains “alive”until there are no proxies referring to it in the whole system. Active object pointersand references also count as proxies, so an active object is not destroyed if even onepointer or reference pointing to it remains. Like in all systems based on referencecounting (current Python [van Rossum, 1999], for example), this creates problemswith circular references. If two active objects refer to each other, they are not des-troyed even if the rest of the system does not contain proxies to them. One solutionto this would be to write a “reset” method to one of the objects. This method wouldremove the reference to the other object. The program would then have to call thismethod explicitly when the objects are no longer needed.

The reason for reference counting in KC++ is again simplicity and efficiency. Ref-erence counts are easy to keep up to date and do not require a distributed garbage-collection system to decide when an active object may be destroyed. Current distrib-uted garbage-collectors are quite complex and non-efficient, so they would probablynot be suitable for a C++-based system.

Another possibility would have been to use the normal C++ semantics and des-troy an active object when the “primary” proxy (the proxy which originally createdthe object) is destroyed. This approach also has its problems because of concurrency.As discussed in Section 4.3.1, it is possible to create an active object, pass a refer-ence to it to another active object as a method parameter, and then destroy the object.The method call is asynchronous, so it is possible (and even probable) that the ref-erence parameter would still be in use when the primary proxy destroys the activeobject. This approach is used in CC++ [Kesselman, 1996], which also suffers from theproblem described above.

8.2.3 No subtyping with non-active bases

When an active class is derived only from the base class Active or other active classes,normal C++ subtyping rules are respected, and an instance of the derived active classis also an instance of the base active class (the “is-a” relationship). Unfortunatelythis relationship cannot be allowed between a normal non-active base class and anactive class derived from it. The reasons for this restriction were discussed in detailin Section 3.3, but the main point is that a derived active object is not an instance ofa non-active base class because of asynchronous method calls.

The lack of “non-active” subtyping is sometimes unfortunate if the programmerwants to “drop in” active classes into an existing program by deriving these activeclasses from existing non-active classes, and then use these classes through base classpointers or references. However, it is almost always impossible to simply add concur-

103

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

rency into an existing program without restructuring and reanalysing the existingcode, so the restriction in KC++ is not likely to cause problems.

8.2.4 The future relaying mechanism

KC++ allows passing pending futures from an active object to another. When the valueof a future becomes available, the active object forwards the value of the future to allother active objects to which it had passezd the original future. These active objectsmay in turn again forward the value. If this chain of active objects gets very long,value forwarding may cause considerable traffic between active objects, especially ifthe value is a large non-active object or a data structure.

In the worst possible case only the last active object in the chain actually needsthe value, and all the other active objects just pass on the value without storing it. Inthis case the value will nevertheless pass through the whole chain of active objectson its way to the final destination.

It would be possible to coordinate the future value passing better, so that thecreator of the pending future would always be informed when another active objectbecomes interested in the value of the future, and when an active object loses interestin it. This “notification mechanism” would be useful with long future chains, but ittoo would add to the traffic between active objects. With short chains and simplefuture values the notification mechanism could actually increase communicationsinstead of decreasing it. It was therefore considered that the simple forwarding systemused in KC++ was enough.

8.3 Future work

Like any piece of software, KC++ is probably never “complete and perfect.” This sec-tion mentions some improvements and changes which are planned for the futureversions of KC++.

It has already been mentioned that the reference counting system, which is usedto determine the lifetime of active objects, causes problems with circular referencechains. One possible solution would be to follow normal C++ semantics and let theactive object be destroyed when the proxy which created it gets destroyed, even ifthere are still pointers or references pointing to it elsewhere. Specialisations of C++auto ptr template could then be used to allow the “ownership” of the active object tobe passed from one place to another. Similarly specialised smart pointer classes couldbe added to allow “shared ownership” using a reference counting solution similar tothe current one.

The current implementation of KC++, and especially its run-time library, is onlymeant for testing purposes. In order to test the performance of the KC++ system, a“quality” implementation of the KC++ library is obviously needed. The implementa-

104

CHAPTER 8. CONCLUSIONS AND FUTURE WORK

tion should also be optimised as much as possible so that performance measurementscould be carried out.

Exceptions are not currently propagated between active objects. However, excep-tions are rapidly becoming a standard way of reporting errors in C++, so it would beimportant to allow active object methods to report error situations to the caller usingexceptions. It is not easy to add exceptions to KC++, because method calls are asyn-chronous. This means that the caller has already continued executing its code (andmaybe already left some try-blocks), making it difficult to decide where in the codethe exception should propagate.

One possible future direction would also be to abandon the strict RTC semanticsand allow active object methods to pause their execution and allow another methodto be executed until a certain synchronisation condition is fulfilled (i.e. probably afuture gets its value). This would mean that a single active object could have severalnon-completed methods, and therefore several threads of execution. However, thischange requires probably more work than others mentioned in this section.

105

BIBLIOGRAPHY

Bibliography

[Ada, 1995] Intermetrics, Inc. Ada 95 Reference Manual, December 1995.

[Agha et al., 1993] Gul Agha, Peter Wegner, and Akinori Yonezawa, editors. ResearchDirections in Concurrent Object-Oriented Programming. The MIT Press, Cambridge(MA), USA, 1993.

[Andrews, 1991] Gregory R. Andrews. Concurrent Programming — Principles andPractice. Addison-Wesley, Reading, Massachusetts, 1991.

[Arnold and Gosling, 1998] Ken Arnold and James Gosling. The Java ProgrammingLanguage. Addison-Wesley, Reading, Massachusetts, 1998.

[Austern, 1998] Matthew H. Austern. Making the world safe for exceptions. C++Report, pages 30–39, January 1998.

[Bezivin, 1987] Jean Bezivin. Some experiments in object-oriented simulation. InMeyrowitz [1987], pages 394–405.

[Briot et al., 1998] Jean-Pierre Briot, Rachid Guerraoui, and Klaus-Peter Lohr. Con-currency and distribution in object-oriented programming. ACM Computing Sur-veys, 30(3):291–329, September 1998.

[Briot, 1989] Jean-Pierre Briot. Actalk: a testbed for classifying and designing actorlanguages in the Smalltalk-80 environment. In Stephen Cook, editor, Proceedingsof the Third European Conference on Object-Oriented Programming (ECOOP 89),British Computer Society Workshop Series, pages 109–129. Cambridge UniversityPress, July 1989.

[Budd, 1997] Timothy Budd. An Introduction to Object-oriented Programming, 2ndedition. Addison-Wesley, Reading, Massachusetts, 1997.

[Cargill, 1994] Tom Cargill. Exception handling: A false sense of security. C++ Report,9(6), November–December 1994.

[Caromel et al., 1996] Denis Caromel, Fabrice Belloncle, and Yves Roudier. C++//. InWilson and Lu [1996], pages 257–296.

106

BIBLIOGRAPHY

[Chandra et al., 1996] Rohit Chandra, Anoop Gupta, and John L. Hennessy. COOL.In Wilson and Lu [1996], pages 215–256.

[Chandy and Kesselman, 1993] K. Mani Chandy and Carl Kesselman. CC++: A de-clarative concurrent object-oriented programming notation. In Agha et al. [1993],pages 281–313.

[Chang et al., 1996] Chialin Chang, Alan Sussman, and Joel Saltz. CHAOS++. InWilson and Lu [1996], pages 131–174.

[Chien and Dolby, 1996] Andrew A. Chien and Julian T. Dolby. ICC++. In Wilson andLu [1996], pages 343–382.

[Coplien, 1992] James O. Coplien. Advanced C++. Addison-Wesley, Reading, Mas-sachusetts, 1992.

[Coplien, 2000] James O. Coplien. C++ idioms. In Neil Harrison, Brian Foote, andHans Rohnert, editors, Pattern Languages of Program Design 4. Addison-Wesley,Reading, Massachusetts, 2000.

[EDG, 1999] Edison Design Group. The C++ Front End, December 1999. http://www.edg.com/cpp.html.

[Gamma et al., 1996] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Pat-terns: Elements of Reusable Object-oriented Software. Addison Wesley, Reading,1996.

[GNU, 2000] GNU. The GNU compiler collection (GCC). http://www.gnu.org/software/gcc/gcc.html, March 2000.

[Goldberg and Robson, 1983] Adele Goldberg and David Robson. Smalltalk-80: TheLanguage and its Implementation. Addison-Wesley, Reading, Massachusetts, 1983.

[Grimshaw et al., 1996] Andrew S. Grimshaw, Adam Ferrari, and Emily A. West.Mentat. In Wilson and Lu [1996], pages 383–428.

[Halstead, 1985] Robert H. Halstead. Multilisp: A language for concurrent sym-bolic computation. ACM Transactions on Programming Languages and Systems,7(4):501–538, October 1985.

[Hoare, 1974] C. A. R. Hoare. Monitors: An operating system structuring concept.Communications of the ACM, 17(10):549–557, October 1974.

[Interplay, 1999] Interplay Productions. Planescape: Torment, 1999.http://www.planescape-torment.com.

[Ishikawa et al., 1996] Yutaka Ishikawa, Atsushi Hori, Hiroshi Tezuka, MotohikoMatsuda, Hiroki Konaka, Munenori Maeda, Takashi Tomokiyo, Jorg Nolte, and Mit-suhisa Sato. MPC++. In Wilson and Lu [1996], pages 429–464.

107

BIBLIOGRAPHY

[ISO, 1998] ISO/IEC. International Standard 14882 – Programming Languages – C++,September 1998.

[Kale and Krishnan, 1996] Laxmikant V. Kale and Sanjeev Krishnan. CHARM++. InWilson and Lu [1996], pages 175–214.

[Kesselman, 1996] Carl Kesselman. CC++. In Wilson and Lu [1996], pages 91–130.

[Kiczales et al., 1991] Gregor Kiczales, Jim des Rivieres, and Daniel G. Bobrow. TheArt of the Metaobject Protocol. The MIT Press, Cambridge (MA), USA, 1991.

[Kreft and Langer, 1998] Klaus Kreft and Angelika Langer. The auto_ptr class tem-plate. C++ Report, 10(10):15–21, November–December 1998.

[Larus et al., 1996] James R. Larus, Brad Richards, and Guhan Viswanathan. C**. InWilson and Lu [1996], pages 297–342.

[Lavender and Schmidt, 1995] R. Greg Lavender and Douglas C. Schmidt. Activeobject: An object behavioral pattern for concurrent programming. In Vlissides et al.[1996], chapter 30.

[Lea, 1997] Doug Lea. Concurrent Programming in Java. The Java Series.Addison-Wesley, Reading, Massachusetts, 1997.

[Lippman, 1996] Stanley B. Lippman. Inside the C++ Object Model. Addison-Wesley,Reading, Massachusetts, 1996.

[Martin, 1997] Robert C. Martin. Design patterns for dealing with dual inheritancehierarchies in C++. C++ Report, April 1997.

[Matsuoka and Yonezawa, 1993] Satoshi Matsuoka and Akinori Yonezawa. Analysisof inheritance anomaly in object-oriented concurrent programming languages. InAgha et al. [1993], pages 107–150.

[Meyer, 1992] Bertrand Meyer. Eiffel: The Language. Prentice Hall, 1992.

[Meyer, 1997] Bertrand Meyer. Object-Oriented Software Construction, second edi-tion. Prentice Hall, 1997.

[Meyers, 1996] Scott Meyers. More Effective C++. Addison-Wesley, Reading, Mas-sachusetts, 1996.

[Meyers, 1998] Scott Meyers. Effective C++ 2nd edition. Addison-Wesley, Reading,Massachusetts, 1998.

[Meyrowitz, 1987] Norman Meyrowitz, editor. OOPSLA ’87 Conference Proceedings.ACM, Addison-Wesley, October 1987.

[O’Farrell et al., 1996] William G. O’Farrell, Frank Ch. Eigler, S. David Pullara, andGregory V. Wilson. ABC++. In Wilson and Lu [1996], pages 1–42.

108

BIBLIOGRAPHY

[OMG, 1998a] Object Management Group, Inc., 492 Old Connecticut Path, Framing-ham, MA 01701. The Common Object Request Broker: Architecture and Specifica-tion, 1998. version 2.2.

[OMG, 1998b] Object Management Group, Inc., 492 Old Connecticut Path, Framing-ham, MA 01701. Messaging Service Specification, May 1998. OMG Documentorbos/98-05-05.

[Reeves, 1996] Jack W. Reeves. Coping with exceptions. C++ Report, March 1996.

[Reynders et al., 1996] John V. Reynders, Paul J. Hinker, Julian C. Cummings,Susan R. Atlas, Subhankar Banerjee, William F. Humphrey, Steve R. Karmesin,Katarzyna Keahey, M. Srikant, and MaryDell Tholburn. POOMA. In Wilson andLu [1996], pages 547–588.

[Rogue Wave, 2000] Rogue Wave Software, Inc. Threads.h++, March 2000. http://www.roguewave.com/products/threads/.

[Rohnert, 1996] Hans Rohnert. The proxy design pattern revisited. In Vlissides et al.[1996], chapter 7.

[Schmidt and Vinoski, 1998] Doug Schmidt and Steve Vinoski. An introduction toCORBA messaging. C++ Report, pages 22–27, November–December 1998.

[Schmidt and Wang, 1995] Douglas C. Schmidt and Nanbor Wang. An OO encapsu-lation of lightweight OS concurrency mechanisms in the ACE toolkit. TechnicalReport WUCS-95-31, Washington University, St. Louis, 1995.

[Schmidt, 1999] Douglas C. Schmidt. Strategized locking, thread-safe interface, andscoped locking: Patterns and idioms for simplifying multi-threaded C++ compon-ents. C++ Report, 11(9), September 1999.

[Sheffler, 1996] Thomas J. Sheffler. The Amelia vector template library. In Wilsonand Lu [1996], pages 43–90.

[Skjellum et al., 1996] Anthony Skjellum, Ziyang Lu, Purushotham V. Bangalore, andNathan Doss. MPI++. In Wilson and Lu [1996], pages 465–506.

[Stroustrup, 1994] Bjarne Stroustrup. The Design and Evolution of C++. Addison-Wesley, Reading, Massachusetts, 1994.

[Stroustrup, 2000] Bjarne Stroustrup. Bjarne Stroustrup’s FAQ. http://www.research.att.com/~bs/bs_faq.html, March 2000.

[Sun, 1996] Sun Microsystems, Inc. Sun Workshop Documentation: C++ Library Ref-erence, 1996.

[Sutter, 1997] Herb P. Sutter. More exception-safe generic containers. C++ Report,pages 25–31, November–December 1997.

109

BIBLIOGRAPHY

[Sutter, 2000] Herb Sutter. Exceptional C++. C++ In-Depth Series. Addison-Wesley,2000.

[Thompson, 1998] Patrick Thompson. IOUs: A future implementation. C++ Report,pages 29–32, July–August 1998.

[van Rossum, 1999] Guido van Rossum. Python reference manual. http://www.python.org/doc/current/ref/ref.html, July 1999.

[Vlissides et al., 1996] John M. Vlissides, James O. Coplien, and Norman L. Kerth,editors. Pattern Languages of Program Design 2. Addison-Wesley, Reading, Mas-sachusetts, 1996.

[Wilson and Lu, 1996] G. Wilson and P. Lu, editors. Parallel Programming Using C++.MIT Press, Cambridge (MA), USA, 1996.

[Winder et al., 1996] Russel Winder, Graham Roberts, Alistair McEwan, JonathanPoole, and Peter Dzwig. UC++. In Wilson and Lu [1996], pages 629–670.

[Yang et al., 1996] Shelby X. Yang, Dennis Gannon, Peter Beckman, Jacob Gotwals,and Neelakantan Sundaresan. pC++. In Wilson and Lu [1996], pages 507–546.

[Yokote and Tokoro, 1987] Yasuhiko Yokote and Mario Tokoro. Experience and evol-ution of ConcurrentSmalltalk. In Meyrowitz [1987], pages 406–415.

110

APPENDIX A. PRODUCER–CONSUMER BUFFER FROM NON-ACTIVE BASE

Appendix A

Producer–consumer buffer fromnon-active base

This appendix provides an example, which shows how active classes can be cre-ated from existing non-active classes using multiple inheritance. The example alsodemonstrates why this usually creates problems, caused by the fact that non-activeclasses are not designed with concurrency in mind.

Below is a definition of class Buffer, which is a simple class which allows usersto put integers into the buffer and later read them (in FIFO order). The code is fromfile oldbuffer.h.

1 #include <list>2

3 // A simple buffer4 class Buffer5 {6 public:7 Buffer();8 ~Buffer();9

10 void put(int value); // Writers use this11

12 bool isEmpty() const; // true if the buffer is empty13 int get(); // Readers use this14 private:15 list<int> values;16 };

A new active class can be created from Buffer by deriving it from both Buffer andthe KC++ class Active. Two other active classes are defined. Producer objects write

111

APPENDIX A. PRODUCER–CONSUMER BUFFER FROM NON-ACTIVE BASE

number from 1 to 10 into the buffer given in the constructor. Consumer objects in turnread integers from the buffer until the buffer becomes empty. Because both classesare active, producers and consumers can operate in parallel. This code is from fileactbuffer.h.

1

2 #include "kcpp_active.h"3

4 #include "oldbuffer.h"5

6 // An active buffer created from the non-active one7 class ActBuffer : public Buffer, public Active8 {9 // No changes necessary, inherits everything from Buffer

10 };11

12 class Producer : public Active13 {14 public:15 Producer(ActBuffer& buffer);16 ~Producer();17

18 void produce(); // This function does the actual producing19 private:20 ActBuffer& buffer ;21 };22

23

24 class Consumer : public Active25 {26 public:27 Consumer(ActBuffer& buffer); // Consumer gets the buffer it reads from28

29 void consume(); // This reads values until buffer is empty30 private:31 ActBuffer& buffer ;32 };

Finally, the next code defines the methods in Producer and Consumer. The actualbuffer class ActBuffer does not need any code, because it inherits everything fromBuffer.

The Producer::produce code is quite simple. It simply calls put ten times. Becausethe buffer is an active object, all method calls are asynchronous and the producerwrites the numbers without waiting for previous put operations to complete.

112

APPENDIX A. PRODUCER–CONSUMER BUFFER FROM NON-ACTIVE BASE

The code in Consumer::consume is more complex. Data can be read from the ori-ginal Buffer class by first checking that data exists (isEmpty) and then reading it(get). However, ActBuffer is an active object, so it is possible that there are severalconsumers working in parallel. If the normal non-parallel algorithm would be used,one consumer could check that the buffer is not empty, then another consumer couldread the value, emptying the buffer. After this the first consumer would attempt toread a value from a now empty buffer, causing an error condition. This problem canbe solved by locking the buffer object for the duration of the isEmpty–get pair.

This code also reveals that the original buffer class was not really suited for paral-lel use. If both producers and consumers work in parallel, it is possible that the bufferbecomes temporarily empty, even if producers still exist. The ActBuffer class has noway of noticing this, so it cannot inform the consumers that more values will becomeavailable. Appendix C contains another buffer example which is designed for paralleluse.

1 #include "kcpp_activelock.h"2

3 #include "actbuffer.h"4

5 // No code needed for the ActBuffer class6

7 // The Producer class8 Producer::Producer(ActBuffer& buffer) : buffer (buffer)9 {

10 }11

12 Producer::~Producer()13 {14 }15

16 void Producer::produce()17 {18 for (int i=0; i<10; ++i)19 {20 buffer .put(i);21 }22 }23

24

25 // The Consumer class26 Consumer::Consumer(ActBuffer& buffer) : buffer (buffer)27 {28 }

113

APPENDIX A. PRODUCER–CONSUMER BUFFER FROM NON-ACTIVE BASE

29

30 void Consumer::consume()31 {32 // Reads values until the buffer is empty33 while (true)34 {35 // The buffer must be locked so that nobody “steals” the value36 ActiveLock lock(buffer ); // (Section 5.6)37

38 if (buffer .isEmpty()) { break; } // Leave if buffer empty39

40 int value = buffer .get(); // Synchronisation41 // Here value could be used42 }43 }

114

APPENDIX B. PRIORITY SEMAPHORE AS AN ACTIVE OBJECT

Appendix B

Priority semaphore as an activeobject

This appendix presents a “priority semaphore” synchronisation mechanism imple-mented as an active object. It shows how future sources can be used and how C++containers can be used with futures. The P operation in the semaphore returns avoid-future. The caller of P is expected to wait using the return value future (i.e.sem.P().wait()). Alternatively the caller may of course poll the future occasionally.The V operation then binds this future source and releases the waiter. The P operationhas a priority as its parameter, and waiters are released in priority order.

The semaphore contains a priority queue — a “wait queue” which contains fu-ture source-priority pairs. It also contains a count which counts “free” resources (i.e.the normal counter used in non-binary semaphores). The P operation first checkswhether there are free resources. If the counter is above zero (free resources exist),the operation simply returns an already bound future, so that the caller is releasedimmediately. If the counter is zero, the operation adds another future source-prioritypair to the queue, and returns a void-future created from the future source.

Similarly the V operation first checks whether the wait queue is empty. If it is, theoperation simply increments the counter. Otherwise it takes the first future source(with the highest priority) from the queue and binds it, releasing the waiter.

115

APPENDIX B. PRIORITY SEMAPHORE AS AN ACTIVE OBJECT

1 #include <deque>2 #include <queue>3 #include <utility> // For pair and make pair4

5 #include "kcpp_active.h"6 #include "kcpp_future.h"7

8 class Semaphore : public Active9 {

10 public:11 Semaphore(unsigned int newcount);12

13 // Callers of P may give a waiting priority14 Future<void> P(int priority = 0);15 void V();16 private:17 // The type of queue items (priority-future source pairs)18 typedef pair<int, SFuture<void> > QueueItem;19

20 // A function object for comparing queue items21 struct QueueCompare22 {23 bool operator ()(const QueueItem& q1, const QueueItem& q2);24 };25

26 unsigned int count;27 priority queue<QueueItem, deque<QueueItem>,28 QueueCompare> waitqueue;29 };30

31 // The queue item comparison for ordering the items32 bool Semaphore::QueueCompare::operator ()33 (const QueueItem& q1, const QueueItem& q2)34 {35 return q1.first < q2.first; // Compare only priorities36 }37

38 Semaphore::Semaphore(unsigned int newcount)39 : count(newcount), waitqueue()40 {41 }42

43 Future<void> Semaphore::P(int priority)

116

APPENDIX B. PRIORITY SEMAPHORE AS AN ACTIVE OBJECT

44 {45 if (count > 0)46 { // No waiting necessary47 −−count;48 return Future<void>(); // Return a non-pending future49 }50 else51 { // Waiting necessary, add to queue52 SFuture<void> waitfut;53 waitqueue.push(make pair(priority, waitfut));54 return waitfut; // Return a pending future55 }56 }57

58 void Semaphore::V()59 {60 if (waitqueue.empty())61 { // Nobody waiting, increment count62 ++count;63 }64 else65 { // Otherwise signal the first waiter66 SFuture<void> waitfut = waitqueue.top().second;67 waitqueue.pop(); // Remove the future source from queue68 waitfut.bind();69 }70 }

117

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

Appendix C

Producer–consumer buffer withfutures

This appendix presents a future-based concurrent buffer. It shows how futures canbe used to provide maximal concurrency. It also solves the problems of the buffer inAppendix A.

Each producer registers itself to the buffer before it begins and unregisters whenit stops. The buffer maintains a count of registered producers. The buffer considersitself “finished” when the buffer is empty, all producers have unregistered, and therehas been at least one producer (so that the buffer is not “finished” right from the start).The buffer also keeps a list of values stored in the buffer. These values are integerfutures, so the producers can promise to provide values into the buffer. The bufferthen simply delivers these “integer promises” to the consumers, letting the KC++ run-time system to do all the dirty work. The description of put and get operations belowshow that futures are also used to let consumers get values the buffer does not evenknow about yet.

Each consumer reads data using the get method. The method returns a futureof a type ValuePair. This pair tells (when its value becomes available) whether thebuffer is finished (field isFinished) and if not, a future which will contain the valueread from the buffer (field value). When get is executed, it first checks whether thevalue list is currently empty, and if not, it simply returns the first value in the list(a future, actually). If the list is empty, the operation checks whether there are anyregistered producers left. If there are none, the buffer is finished and the consumersis informed about this. However, if the value is empty and there still are producers, itis possible that more values will become available. For this reason the buffer containsa “wait list” waiting . In this case a new future source of type ValuePair is createdand added to the list, and the return value of the operation is created from this futuresource.

118

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

When a put operation is executed, it first checks whether the wait list is empty. Ifit is, the value (a future) is simply inserted to the value list. If the wait list containswaiters, the operation binds the first future source in the wait list to the value (afuture) written to the buffer, causing the value to be sent to the caller of the getoperation.

Finally, it is possible that there are still waiters in the wait list when the lastproducer unregisters. In this case all waiters are released and informed about thestatus of the buffer using the private method releaseWaiters.

1 #include <list>2 #include <cassert>3

4 #include "kcpp_active.h"5 #include "kcpp_future.h"6 #include "kcpp_messages.h"7

8 // Struct which is the return value of buffer’s get operation9 struct ValuePair

10 {11 bool isFinished; // All producers have finished and buffer is empty?12 Future<int> value; // Eventual read value, if buffer not finished13

14 // Constructors to make creating ValuePairs easier15 ValuePair() : isFinished(true), value() {}16 ValuePair(bool f, Future<int> v) : isFinished(f), value(v) {}17 // Unmarshalling constructor (Section 4.1)18 inline ValuePair(IMsg& imsg);19 };20

21 inline ValuePair::ValuePair(IMsg& imsg) : isFinished(false), value(imsg)22 {23 imsg >> isFinished; // Must use >> for built-in types24 }25

26 // Marshalling operator27 inline OMsg& operator <<(OMsg& omsg, const ValuePair& p)28 {29 omsg << p.value << p.isFinished; // Order matches the unmarshalling30 return omsg;31 }32

33 // Unmarshalling operator (not actually needed by KC++)34 inline IMsg& operator >>(IMsg& imsg, ValuePair& p)

119

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

35 {36 imsg >> p.value >> p.isFinished;37 return imsg;38 }39

40

41 class Buffer : public Active42 {43 public:44 Buffer();45 ~Buffer();46

47 // Producers use these to register and unregister to/from buffer48 void registerProducer();49 void unregisterProducer();50

51 void put(Future<int> value); // Producers use this52 Future<ValuePair> get(); // Consumers use this53 private:54 void releaseWaiters(); // Releases waiters when all producers have gone55

56 bool justStarted ; // true after creation but before first registration57 unsigned int producerCount ; // How many producers registered58 list< Future<int> > values ; // Place to put written values59 list< SFuture<ValuePair> > waiting ; // Wait queue for consumers60 };61

62 // A dummy active class which (somehow) generates values for the buffer63 class Generator : public Active64 {65 public:66 int generate(int i);

...67 };68

69 class Producer : public Active70 {71 public:72 Producer(Buffer& buffer, Generator& gen);73 ~Producer();74

75 void produce(); // This function does the actual producing76 private:

120

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

77 Buffer& buffer ;78 Generator& gen ;79 };80

81

82 class Consumer : public Active83 {84 public:85 Consumer(Buffer& buffer); // Consumer gets the buffer it reads from86

87 void consume(); // This reads values until buffer finishes88 private:89 Buffer& buffer ;90 };91

92

93 // The Buffer class94 Buffer::Buffer()95 : justStarted (true), producerCount (0) , values (), waiting ()96 {97 }98

99 Buffer::~Buffer()100 {101 releaseWaiters(); // Just release any waiting consumers (if any)102 }103

104 void Buffer::registerProducer()105 {106 assert(justStarted | | producerCount > 0);107 justStarted = false; // At least one producer registered108 ++producerCount ;109 }110

111 void Buffer::unregisterProducer()112 {113 assert(producerCount > 0);114 −−producerCount ;115 if (producerCount == 0) { releaseWaiters(); } // No more producers116 }117

118 void Buffer::put(Future<int> value)119 {120 if (waiting .empty())

121

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

121 { // No consumers waiting, put into the value list122 values .push back(value);123 } else124 { // A consumer is waiting, send the value to it125 waiting .front().bind(false, value);126 waiting .pop front(); // Remove consumer from wait queue127 }128 }129

130 Future<ValuePair> Buffer::get()131 {132 if (!values .empty())133 { // Values in the list, return the first134 Future<int> value(values .front());135 values .pop front();136 return ValuePair(false, value); // Return future get its value here137 } else if (!justStarted && producerCount == 0)138 { // No producers either, we are finished139 return ValuePair(true, Future<int>());140 } else141 { // Otherwise enter the wait queue142 SFuture<ValuePair> waitSource; // New future source for waiting143 waiting .push back(waitSource); // Add it to the wait queue144 return waitSource; // Create return value from the future source145 }146 }147

148 void Buffer::releaseWaiters()149 {150 // Inform all waiters that values have run out151 while (!waiting .empty())152 {153 waiting .front().bind(true, Future<int>()); // Release waiter154 waiting .pop front();155 }156 }157

158

159

160 // The Producer class161 Producer::Producer(Buffer& buffer, Generator& gen)162 : buffer (buffer), gen (gen)163 {164 buffer .registerProducer();

122

APPENDIX C. PRODUCER–CONSUMER BUFFER WITH FUTURES

165 }166

167 Producer::~Producer()168 {169 buffer .unregisterProducer();170 }171

172 void Producer::produce()173 {174 for (int i=0; i<10; ++i)175 {176 buffer .put(gen .generate(i)); // generator returns a future177 }178 }179

180

181 // The Consumer class182 Consumer::Consumer(Buffer& buffer) : buffer (buffer)183 {184 }185

186 void Consumer::consume()187 {188 // Reads values until all producers have unregistered189 while (true)190 {191 Future<ValuePair> result;192 result = buffer .get(); // Read (asynchronous)193

194 // Here might be some other processing195

196 ValuePair valuepair(result); // Synchronisation197 if (valuepair.isFinished) { break; } // Stop if finished198

199 // Here we know that the value will become available200 // Some processing again201

202 int value = valuepair.value; // Synchronisation with the producer’s value203 // Here value could be used204 }205 }

123