You mentioned that your class deals with OS issues. I assume that means you want to use a compiled language. Strangely enough, one does not need to use the C language to create an efficient OS.
Following is my solution to your problem stated above using the Ada programming language.
Ada calls threads tasks. Tasks are built into the language. Tasks are an active unit of concurrency. Ada also provides protected objects. Protected objects are passive units of concurrency. The following example uses a protected object to implement the buffer shared by the producers and consumers. Ada protected objects are implicitly protected from inappropriate simultaneous access of a shared object.
Ada provides packages as its unit of modularity and encapsulation. The following package defines the producer task type and the consumer task type along with the protected object which is the shared buffer. Ada packages are separated into a package specification, which defines the API for the package, and a package body which contains the implementation of the package.
Following is the package specification I created for this problem:
package prodcon is
subtype Count_Type is Integer range 1..100;
type Prod_Id is range 1..10;
task type producer is
entry set_id (Id : Prod_Id);
end producer;
task type consumer is
entry start;
entry report ( total : out Natural);
end consumer;
end prodcon;
The Ada language is case-insensitive regarding reserved words and identifiers. This means the language treats Count_Type, count_type or CoUnT_tYpE as the same identifier.
package specification:
The package declares the following:
- A subtype of Integer named Count_Type. Count_Type has a minimum valid
value of 1 and a maximum valid value of 100.
- A numeric type named Prod_Id with a minimum value of 1 and a
maximum value of 10
- A task type named Producer. Producer has one entry, which will be
explained later
- A task type named Consumer. Consumer has two entries, which will
also be explained later.
Count_Type defines the range of values that can be written to the buffer by a producer instance and read from the buffer by a consumer instance.
Prod_Id defines the range of producer identifier values to be used in this program.
Task types are much like any other user-defined type. They identify the data and operations of a particular type. One or more instances of that type may be used in a program. By defining producer as a task type and consumer as a different task type the program can create an arbitrary number of producers or consumers.
task entries:
Task entries provide a direct communication mechanism between tasks. Entries implement a rendezvous between the task calling the entry and the task accepting the entry. The rendezvous mechanism causes the calling task and the accepting task to synchronize at the entry point. The first task to the entry point suspends until the other task also reaches the entry point. Data can be passed between the two tasks then they resume their concurrent execution.
The producer task type declares one entry named set_id, which passes a value of type Prod_Id to the producer task.
The consumer task type declares two entries. The first entry is named start. The start entry in this example passes no value. The second entry is named report. The report entry passes the total calculated by a consumer back to the calling task.
All the synchronization activities involved in a rendezvous are written by the compiler.
package body
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
package body prodcon is
package rand_count is new Ada.Numerics.Discrete_Random (Count_Type);
use rand_count;
type Index is mod 50;
type circular_array is array (Index) of Count_Type;
protected buffer is
entry write (value : in Count_Type);
entry read (value : out Count_Type);
procedure Register;
procedure De_Register;
function Count return Natural;
function has_producers return Boolean;
private
Prod_Count : Natural := 0;
Buf : circular_array;
Write_Idx : Index := 0;
Read_Idx : Index := 0;
Val_Count : Natural;
end buffer;
protected body buffer is
entry write (value : in Count_Type) when Val_Count < Index'Modulus is
begin
Buf (Write_Idx) := value;
Write_Idx := Write_Idx + 1;
Val_Count := Val_Count + 1;
end write;
entry read (value : out Count_Type) when Val_Count > 0 is
begin
value := Buf (Read_Idx);
Read_Idx := Read_Idx + 1;
Val_Count := Val_Count - 1;
end read;
procedure Register is
begin
Prod_Count := Prod_Count + 1;
end Register;
procedure De_Register is
begin
Prod_Count := Prod_Count - 1;
end De_Register;
function Count return Natural is
begin
return Val_Count;
end Count;
function has_producers return Boolean is
begin
return Prod_Count > 0;
end has_producers;
end buffer;
--------------
-- producer --
--------------
task body producer is
My_Id : Prod_Id;
Num : Count_Type;
Seed : Generator;
Max_Iter : Count_Type;
Sum : Natural := 0;
begin
accept set_id (Id : Prod_Id) do
My_Id := Id;
end set_id;
buffer.Register;
Reset (Seed);
Max_Iter := Random (Seed);
for I in 1 .. Max_Iter loop
Num := Random (Seed);
buffer.write (Num);
Sum := Sum + Num;
end loop;
buffer.De_Register;
Put_Line
("The sum of values generated by producer" & My_Id'Image & " is" &
Sum'Image);
end producer;
--------------
-- consumer --
--------------
task body consumer is
Value : Count_Type;
Sum : Natural := 0;
begin
accept start;
while buffer.has_producers
or else (not buffer.has_producers and buffer.Count > 0)
loop
select
buffer.read (Value);
Sum := Sum + Value;
else
null;
end select;
end loop;
accept report (Total : out Natural) do
Total := Sum;
end report;
end consumer;
end prodcon;
The contents of the package body are not visible to any package or subprogram that depends upon the package. This is similar to being private in Java.
The package body depends upon the Ada.Text_IO package to perform I/O to standard output. It also depends upon the generic package Ada.Numerics.Discrete_Random to produce random integers in a specified range.
Within the prodcon package body an instantiation of the Ada.Numerics.Discrete_Random package is made. The subtype Count_Type is passed to the package so that the random numbers generated will be within the range of 1 through 100.
A modular type named Index is defined. Index is defined to be "mod 50". The result of this is an unsigned type with a range of values from 0 through 49. Modular types exhibit wrap-around arithmetic. If the value of an instance of Index is 49 and that value is incremented the result will be 0. All arithmetic on and instance of a modular type results in a value within the range of the modular type.
An array type named circular_array is defined to be indexed by the modular type Index and to contain elements of the subtype Count_Type. This array type will be used within the protected object to implement our required circular buffer.
Protected objects are written with a specification and a body in a manner very similar to the way packages are written. The protected type specification contains the API for its behaviors in its public part and a definition of its data members in its private part.
Protected types may have three kinds of methods:
- procedures -- procedures employ an exclusive read-write lock
- entries -- entries employ an exclusive read-write lock and also have
a barrier condition which must be true before the calling task can
execute the entry. The calling task will be suspended in an entry
queue provided by the compiler until the barrier condition evaluates
to TRUE or the calling task removes itself from the entry queue.
- functions -- functions employ a shared read lock and must return a
value without changing the values within the protected object.
The entry write passes an instance of Count_Type to the buffer.
The entry read passes an instance of Count_Type to the calling task.
The procedure Register registers a producer with the buffer so that the buffer contains a count of the number of producers currently using the buffer.
The procedure De_Register removes a producer from the count of producers using the buffer.
The function Count returns the number of values currently in the buffer.
The function has_producers returns True if the count of producers using the buffer is greater than 0.
The private part of the protected type contains all the data members used in the protected type.
The protected body provides the implementation of all the entries, procedure and functions declared in the protected specification. Observe that the Write_Idx used in the write entry and the Read_Idx used in the read entry take advantage of the modular arithmetic associated with type Index. The boundary condition of the write entry states that the entry can be executed when Val_Count is less than Index'Modulus. In this case Index'Modulus is 50. The boundary condition of the read entry states that the entry can be executed when Val_Count is greater than 0.
The Register procedure increments the Prod_Count data member while the De_Register procedure decrements the Prod_Count data member.
task body producer:
The producer task contains five local variables. The executable portion of the task begins by accepting the set_id task entry. The task will suspend until its set_id entry is called by another task. The main procedure executes in the environment task and will call the set_id entry for each producer instance.
Once the set_id entry completes the producer instance calls buffer.register, computes a random number from 1 through 100 for the maximum number of iterations it will execute then enters a for loop. Each iteration of the loop it generates a random number from 1 through 100, writes that number to buffer and finally computes a sum of all the values it generates.
Upon completing the loop the producer task de-registers with buffer and outputs its local total of values produced.
The consumer task body declares two local variables; Value which is an instance of Count_Type and Sum which is an instance of Natural. Natural is a subtype of Integer with a minimum value of 0.
The executable part of consumer begins by accepting its start entry. This causes the consumer to wait until it is commanded to proceed. This entry is necessary so that the consumer may be started after all the producers have started.
The major work of the consumer is performed within the while loop. The consumer keeps reading values until buffer.has_producers is false and buffer.count is 0.
When there are multiple consumers it is possible to a consumer to attempt to read from an empty buffer and be suspended forever because it is possible for consumer A to check whether the buffer is empty and then call buffer.read only to have the buffer emptied by consumer B before Consumer A gets its turn at reading the buffer.
The Ada language provides a select statement which surrounds the attempt to read from the buffer. In this case, if the consumer is not immediately given access to the buffer it cancels its entry call and attempts another iteration.
Once the consumer has read all the values it can read it accepts the report task entry through which it passes its subtotal out to the calling task.
main procedure:
The main procedure for this program is called main in this example. Ada allows the main procedure to be named whatever you want to name it. For instance, it could have been named "test" or something else.
with prodcon; use prodcon;
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type prod_list is array(prod_id range <>) of producer;
type cons_list is array(prod_Id range <>) of consumer;
Total : Natural := 0;
Sum : Natural;
producers : prod_list (1..1);
consumers : cons_list (1..10);
begin
for I in producers'range loop
producers(I).set_Id (I);
end loop;
for I in consumers'Range loop
Consumers (I).Start;
end loop;
for C of Consumers loop
C.Report(Sum);
Total := Total + Sum;
end loop;
Put_Line("The total of all values produced is" & total'Image);
end Main;
The main procedure declares a dependency upon the prodcon package and a dependency upon the Ada.Text_IO package.
Within the declaration portion of the main procedure two array types are declared. The first array type is named prod_list. Prod_list is defined as an unconstrained array type indexed by the type prod_id. Each element of prod_list is an instance of the producer task type. The second array type is named cons_list. It also is defined as an unconstrained array type indexed by prod_list. Each element of cons_list is an instance of the consumer task type.
An unconstrained array type allows creation of instances of the array type with different numbers of elements.
Four data items are declared.
- Total is an instance of natural initialized to 0. Total will contain
the total of all the values reported by all the consumer instances.
- Sum is an instance of Natural. Sum will contain the individual value
reported by each instance of consumer.
- producers is an instance of prod_list with index values starting at 1
and ending at 1. It is an array with a single element.
- consumers is an instance of cons_list with index values starting at 1
and ending at 10. It is an array containing 10 elements.
Within the executable portion of the main procedure are three for loops. The first for loop sets the Id values for each producer in the producers array. The second for loop starts each of the consumer elements in the consumers array. The third for loop is much like a for-each loop in other languages. It calls the report entry for each element of the consumers array, totaling the values reported. Finally, the total of all values reported by the consumers is output.
sample outputs:
Since the producers generate a random number of random values each execution of the program will output different results. Following is an example of an output of the program shown above with 1 producer and 10 consumers.
The sum of values generated by producer 1 is 2695
The total of all values produced is 2695
When the main procedure is modified to have 10 producers and 10 consumers the sample output is:
The sum of values generated by producer 1 is 928
The sum of values generated by producer 7 is 273
The sum of values generated by producer 9 is 191
The sum of values generated by producer 2 is 3377
The sum of values generated by producer 5 is 2565
The sum of values generated by producer 3 is 2860
The sum of values generated by producer 8 is 2761
The sum of values generated by producer 4 is 3749
The sum of values generated by producer 6 is 3268
The sum of values generated by producer 10 is 3899
The total of all values produced is 23871