$30.3 FROM PROCESSES TO OBJECTS 961 feature--Process behavior live is --Do the printer thing do from setup until stop requested loop wait for job,print (oldest),remove oldest end end ..Other features... end--class PRINTER I Note the provision for Other features:although so far live and the supporting features have claimed all our attention,we can endow processes with many other features if we want to,encouraged by the O-O approach developed elsewhere in this book.Turning PRINTER objects into processes would mean limiting this freedom;that would be a major loss of expressive power,without any visible benefit. By abstracting from this example,which describes a particular process type simply as a class,we can try to provide a more general description of all process types through a deferred class-a behavior class as we have often encountered in previous chapters. Procedure live will apply to all processes.We could leave it deferred,but it is nottoo much of a commitment to note that most processes will need some initialization,some termination,and in-between a basic step repeated some number of times.So we can already effect a few things at the most abstract level: indexing description:"The most general notion of process" deferred class PROCESS feature--Status report over:BOOLEAN is --Must execution terminate now? deferred end feature--Basic operations setup is --Prepare to execute process operations (default:nothing). do end step is --Execute basic process operations. deferred end
§30.3 FROM PROCESSES TO OBJECTS 961 feature -- Process behavior live is -- Do the printer thing. do from setup until stop_requested loop wait_ for_job; print (oldest); remove_oldest end end … Other features … end -- class PRINTER_1 Note the provision for Other features: although so far live and the supporting features have claimed all our attention, we can endow processes with many other features if we want to, encouraged by the O-O approach developed elsewhere in this book. Turning PRINTER_1 objects into processes would mean limiting this freedom; that would be a major loss of expressive power, without any visible benefit. By abstracting from this example, which describes a particular process type simply as a class, we can try to provide a more general description of all process types through a deferred class — a behavior class as we have often encountered in previous chapters. Procedure live will apply to all processes. We could leave it deferred, but it is not too much of a commitment to note that most processes will need some initialization, some termination, and in-between a basic step repeated some number of times. So we can already effect a few things at the most abstract level: indexing description: "The most general notion of process" deferred class PROCESS feature -- Status report over: BOOLEAN is -- Must execution terminate now? deferred end feature -- Basic operations setup is -- Prepare to execute process operations (default: nothing). do end step is -- Execute basic process operations. deferred end
962 CONCURRENCY,DISTRIBUTION,CLIENT-SERVER AND THE INTERNET $30.3 wrapup is Execute termination operations (default:nothing). do end feature--Process behavior live is -Perform process lifecycle. do from setup until over loop step end wrapup end end -class PROCESS A point of methodology:whereas step is deferred,serup and wrapup are effective procedures,defined as doing nothing.This way we force every effective descendant to provide a specific implementation of step,the basic process action;but in the not infrequent cases that require no particular setup or termination operation we avoid bothering the descendants.This choice between a deferred version and a null effective version occurs regularly in the design of deferred classes,and you should resolve it based on your appreciation of the likely characteristics of descendants.A wrong guess is not a disaster;it will just lead to more effectings or more redefinitions in descendants. From this pattern we may define a more specialized class,covering printers: indexing description:"Printers handling one print job at a time" note:"Revised version based on class PROCESS" class PRINTER inherit PROCESS rename over as stop requested end feature--Status report stop requested:BOOLEAN --Is the next job in the queue a request to shut down? oldest:JOB is -The oldest job in the queue do...end feature--Basic operations step is --Process one job. do wait for job,print (oldest),remove oldest end
962 CONCURRENCY, DISTRIBUTION, CLIENT-SERVER AND THE INTERNET §30.3 wrapup is -- Execute termination operations (default: nothing). do end feature -- Process behavior live is -- Perform process lifecycle. do from setup until over loop step end wrapup end end -- class PROCESS A point of methodology: whereas step is deferred, setup and wrapup are effective procedures, defined as doing nothing. This way we force every effective descendant to provide a specific implementation of step, the basic process action; but in the not infrequent cases that require no particular setup or termination operation we avoid bothering the descendants. This choice between a deferred version and a null effective version occurs regularly in the design of deferred classes, and you should resolve it based on your appreciation of the likely characteristics of descendants. A wrong guess is not a disaster; it will just lead to more effectings or more redefinitions in descendants. From this pattern we may define a more specialized class, covering printers: indexing description: "Printers handling one print job at a time" note: “Revised version based on class PROCESS" class PRINTER inherit PROCESS rename over as stop_requested end feature -- Status report stop_requested: BOOLEAN -- Is the next job in the queue a request to shut down? oldest: JOB is -- The oldest job in the queue do … end feature -- Basic operations step is -- Process one job. do wait_for_job; print (oldest); remove_oldest end
$30.3 FROM PROCESSES TO OBJECTS 963 wait for job is Wait until job queue is not empty. do ensure oldest /Void end remove oldest is --Remove oldest job from queue. require oldest /Void do if oldest.is stop request then stop requested:=True end "Remove oldest from queue" end print (j:JOB)is --Print j,unless it is just a stop request require j/=Void do if not j.is_stop_request then"Print the text associated with "end end end--class PRINTER Exercise E30.1. The class assumes that a request to shut off the printer is sent as a special print job/ page 1035. for which j.is_stop request is true.(It would be cleaner to avoid making print and remove oldest aware of the special case of the"stop request"job;this is easy to improve.) The benefits of O-O modeling are apparent here.In the same way that going from main program to classes broadens our perspective by giving us abstract objects that are not limited to"doing just one thing",considering a printer process as an object described by a class opens up the possibility of new,useful features.With a printer we can do more than execute its normal printing operation as covered by live (which we should perhaps have renamed operate when inheriting it from PROCESS);we might want to add such features as perform internal test,switch to Postscript level I or set resolution.The equalizing effect of the O-O method is as important here as in sequential software. More generally,the classes sketched in this section show how we can use the normal object-oriented mechanisms -classes,inheritance,deferred elements,partially implemented patterns-to implement processes.There is nothing wrong with the concept of process in an O-O context;indeed,we will need it in many concurrent applications.But rather than a primitive mechanism it will simply be covered by a library class PROCESS based on the version given earlier in this section,or perhaps several such classes covering variants of the notion. For the basic new construct of concurrent object technology,we must look elsewhere
§30.3 FROM PROCESSES TO OBJECTS 963 wait_for_job is -- Wait until job queue is not empty. do … ensure oldest /= Void end remove_oldest is -- Remove oldest job from queue. require oldest /= Void do if oldest ● is_stop_request then stop_requested := True end “Remove oldest from queue” end print (j: JOB) is -- Print j, unless it is just a stop request. require j /= Void do if not j ● is_stop_request then “Print the text associated with j” end end end -- class PRINTER The class assumes that a request to shut off the printer is sent as a special print job j for which j ● is_stop_request is true. (It would be cleaner to avoid making print and remove_oldest aware of the special case of the “stop request” job; this is easy to improve.) The benefits of O-O modeling are apparent here. In the same way that going from main program to classes broadens our perspective by giving us abstract objects that are not limited to “doing just one thing”, considering a printer process as an object described by a class opens up the possibility of new, useful features. With a printer we can do more than execute its normal printing operation as covered by live (which we should perhaps have renamed operate when inheriting it from PROCESS); we might want to add such features as perform_internal_test, switch_to_Postscript_level_1 or set_resolution. The equalizing effect of the O-O method is as important here as in sequential software. More generally, the classes sketched in this section show how we can use the normal object-oriented mechanisms — classes, inheritance, deferred elements, partially implemented patterns — to implement processes. There is nothing wrong with the concept of process in an O-O context; indeed, we will need it in many concurrent applications. But rather than a primitive mechanism it will simply be covered by a library class PROCESS based on the version given earlier in this section, or perhaps several such classes covering variants of the notion. For the basic new construct of concurrent object technology, we must look elsewhere. Exercise E30.1, page 1035
964 CONCURRENCY,DISTRIBUTION,CLIENT-SERVER AND THE INTERNET $30.4 30.4 INTRODUCING CONCURRENT EXECUTION What-if not the notion of process-fundamentally distinguishes concurrent from sequential computation? Processors To narrow down the specifics of concurrency,it is useful to take a new look at the figure which helped us lay the very foundations of object technology by examining the three basic ingredients of computation: The three forces of computation Action bject (This figure first appeared on page 101) Processor To perform a computation is to use certain processors to apply certain actions to certain objects.At the beginning of this book we discovered how object technology addresses fundamental issues of reusability and extendibility by building software architectures in which actions are attached to objects(more precisely,object types)rather than the other way around. What about processors?Clearly we need a mechanism to execute the actions on the objects.But in sequential computation there is just one thread of control,hence just one processor;so it is taken for granted and remains implicit most of the time. In a concurrent context,however,we will have two or more processors.This property is of course essential to the idea of concurrency and we can take it as the definition of the notion.This is the basic answer to the question asked above:processors (not processes)will be the principal new concept for adding concurrency to the framework of sequential object-oriented computation.A concurrent system may have any number of processors,as opposed to just one for a sequential system. The nature of processors Definition:processor A processor is an autonomous thread of control capable of supporting the sequential execution of instructions on one or more objects
964 CONCURRENCY, DISTRIBUTION, CLIENT-SERVER AND THE INTERNET §30.4 30.4 INTRODUCING CONCURRENT EXECUTION What — if not the notion of process — fundamentally distinguishes concurrent from sequential computation? Processors To narrow down the specifics of concurrency, it is useful to take a new look at the figure which helped us lay the very foundations of object technology by examining the three basic ingredients of computation: To perform a computation is to use certain processors to apply certain actions to certain objects. At the beginning of this book we discovered how object technology addresses fundamental issues of reusability and extendibility by building software architectures in which actions are attached to objects (more precisely, object types) rather than the other way around. What about processors? Clearly we need a mechanism to execute the actions on the objects. But in sequential computation there is just one thread of control, hence just one processor; so it is taken for granted and remains implicit most of the time. In a concurrent context, however, we will have two or more processors. This property is of course essential to the idea of concurrency and we can take it as the definition of the notion. This is the basic answer to the question asked above: processors (not processes) will be the principal new concept for adding concurrency to the framework of sequential object-oriented computation. A concurrent system may have any number of processors, as opposed to just one for a sequential system. The nature of processors Definition: processor A processor is an autonomous thread of control capable of supporting the sequential execution of instructions on one or more objects. The three forces of computation (This figure first appeared on page 101.) Action Object Processor
$30.4 INTRODUCING CONCURRENT EXECUTION 965 This is an abstract notion,it should not be confused with that of physical processing device,for which the rest of this chapter will use the term CPU,common in computer engineering to denote the processing units of computers."CPU"is an abbreviation of "Central Processing Unit"even though there is most of the time nothing central about CPUs.You can use a CPU to implement a processor,but the notion of processor is much more abstract and general.A processor can be,for example: A computer(with its CPU)on a network. A task,also called process,as supported on operating systems such as Unix, Windows and many others. A coroutine.(Coroutines,covered in detail later in this chapter,simulate true concurrency by taking turns at execution on a single CPU;after each interruption, each coroutine resumes its execution where it last left it. .A "thread"as supported by such multi-threaded operating systems as Solaris,OS/2 and Windows NT. Threads are mini-processes.A true process can itself contain many threads,which it manages directly;the operating system (OS)only sees the process,not its threads Usually the threads of a process will all share the same address space (in object-oriented terms,they potentially have access to the same set of objects),whereas each process has its own address space.We may view threads as coroutines within a process.The main ad vantage of threads is efficiency:whereas creating a process and synchronizing it with other processes are expensive operations,requiring direct OS intervention(to allocate the address space and the code of the process),the corresponding operations on threads are much simpler,do not involve any expensive OS operations,and so can be faster by a factor of several hundreds or even several thousands. The difference between processors and CPUs was clearly expressed by Henry Lieberman(for a different concurrency model): [Lieberman 1987]. The number of [processors]need not be bounded in advance,and if there are page 22.Square brackets signal differ- too many [processors]for the number of real physical [CPUs]you have on ences in terminolog). your computer system,they are automatically time-shared.Thus the user can pretend that processor resources are practically infinite. To avoid any misunderstanding,be sure to remember that throughout this chapter the "processors"denote virtual threads of control;any reference to the physical units of computation uses the term CPU. At some point before or during you will need to assign computational resources to the processors.The mapping will be expressed by a "Concurrency Control File",as described below,or associated library facilities. Handling an object Any feature call must be handled (executed)by some processor.More generally,any object O2 is handled by a certain processor,its handler;the handler is responsible for executing all calls on 02 (all calls of the form x.f(a)where x is attached to O2)
§30.4 INTRODUCING CONCURRENT EXECUTION 965 This is an abstract notion, it should not be confused with that of physical processing device, for which the rest of this chapter will use the term CPU, common in computer engineering to denote the processing units of computers. “CPU” is an abbreviation of “Central Processing Unit” even though there is most of the time nothing central about CPUs. You can use a CPU to implement a processor; but the notion of processor is much more abstract and general. A processor can be, for example: • A computer (with its CPU) on a network. • A task, also called process, as supported on operating systems such as Unix, Windows and many others. • A coroutine. (Coroutines, covered in detail later in this chapter, simulate true concurrency by taking turns at execution on a single CPU; after each interruption, each coroutine resumes its execution where it last left it.) • A “thread” as supported by such multi-threaded operating systems as Solaris, OS/2 and Windows NT. Threads are mini-processes. A true process can itself contain many threads, which it manages directly; the operating system (OS) only sees the process, not its threads. Usually the threads of a process will all share the same address space (in object-oriented terms, they potentially have access to the same set of objects), whereas each process has its own address space. We may view threads as coroutines within a process. The main advantage of threads is efficiency: whereas creating a process and synchronizing it with other processes are expensive operations, requiring direct OS intervention (to allocate the address space and the code of the process), the corresponding operations on threads are much simpler, do not involve any expensive OS operations, and so can be faster by a factor of several hundreds or even several thousands. The difference between processors and CPUs was clearly expressed by Henry Lieberman (for a different concurrency model): The number of [processors] need not be bounded in advance, and if there are too many [processors] for the number of real physical [CPUs] you have on your computer system, they are automatically time-shared. Thus the user can pretend that processor resources are practically infinite. To avoid any misunderstanding, be sure to remember that throughout this chapter the “processors” denote virtual threads of control; any reference to the physical units of computation uses the term CPU. At some point before or during you will need to assign computational resources to the processors. The mapping will be expressed by a “Concurrency Control File”, as described below, or associated library facilities. Handling an object Any feature call must be handled (executed) by some processor. More generally, any object O2 is handled by a certain processor, its handler; the handler is responsible for executing all calls on O2 (all calls of the form x ● f (a) where x is attached to O2). [Lieberman 1987], page 22. Square brackets signal differences in terminology