Welcome
» NERWous C
» Mel
Writer Zones
While the reader zone from previous chapters deal with the reader's end of a mel buffer, we now introduce the mel writer zones for exclusive access to the writer's end of the mel buffer. We are using the term "mel buffer" even if the mel variable is not buffered (which is the default of a mel declaration) -- an unbuffered mel is a mel with a buffer of one slot.
Let's create a new task called
Process
The following behind-the-scene process occurs:
Let us repeat the conditions that allow the task to get into the writer zone:
Usage
The
Checkout Operation
A task can get off a writer zone by either falling off the closing
Skip Behavior
There are times the logic dictates that copying the value of the eponymous variable to the mel slot is not desired. For example, the
The
The
There is a third way to write the above
Eponymous Local Variable
As introduced in Process, the eponymous local variable
The eponymous local variable is not the same as the locally cached <value> property. The latter contains the mel value from the last time the task successfully reads from the mel. On the other hand, the eponymous local variable starts out as undefined, and is initialized inside the writer zone. If desired, the programmer can initialize the eponymous local variable with the mel <value> property:
Waiting Tasks Wake-Up Order
The previous discussion mentions mel buffers when referring to mel variables. To increase concurrency, a mel variable should be buffered, so that reading can be done on one end of the buffer and writing can be concurrently done at the other end. However by default, a NERWous C mel variable is not buffered; or looking in another way, it has a buffer of one slot.
When the task gets into the writer zone of an unbuffered mel variable, it not only holds off other writer tasks waiting to write, it also holds off any reader task waiting to read due to the mel variable being empty. If the task checks out of the writer zone with a value for the mel variable, the waiting reader task will run first to retrieve the newly deposited value and make the mel variable empty again. The writer task waiting at the top of the writers' queue is run next to deposit a new value. However if the task checks out of the writer zone without depositing a value to the unbuffered mel variable, the writer task at the top of the writers' queue is run first to deposit a value before the waiting reader task can run and empty it.
Writer OR Zones
We have seen reader OR zones. Now let's take a look on writer OR zones. Let's modify
Process
This is the behind-the-scene process:
As Attribute
The
Sometimes, the
Traverse Behavior
The behind-the-scene description above uncovers a subtle difference between
This issue can be resolved by using the random traverse behavior for OR writes:
Writer LIST Zones
Unlike the writer OR zone which only requires either specified mel resource, the writer LIST zone allows the programmer to have a task entering a writer zone with all the requested mel resources.
Let us write a new version of
Writer LIST zones can be checked in and out without touching the eponymous local variables, as seen in this version of
The writer LIST zone should not be used if the programmer has knowledge of round-robin resources requests. These can cause deadlocks. For example, task 1 has hold to mel 1 and waits for mel 2, while task 2 has hold to mel 2 and waits for mel 1. If deadlocks are possible, writer AND zones should be considered.
Writer AND Zones
The writer AND zone should be used when there is a chance of requests for mel resources to cause deadlocks. The requested resources are listed with the
Inside the writer zone, the task makes use of the eponymous local variables that represent the requested mel variables. The processing on checkout of a writer AND zone is the same as for a writer LIST zone described previously.
Both the reader AND zone and the writer AND zone prevent deadlocks from multiple tasks that vie for the same resources in a circular way. Is the waiting problem more prevalent with the reader AND zone, since there is only one mel value at the top of the reading side of a mel buffer? At least at the writer's end, we can increase the buffer size so that multiple writers can deposit their products. However the waiting to enter a writer AND zone is as dire as waiting to enter a reader AND zone. This wait is not to access the available empty buffer slots (which we can increase), but to the top end of the writer's queue (which there is only one, like the top end of the reader's end queue). Once a task is in the writer end zone, it will block other writer tasks to access the writer's end of the queue, even though there may be empty slots for them to deposit their products.
Writer Zone Attributes
The attributes for a mel writer zone wait is similar to the ones for a mel reader zone:
Mode Attribute
The
The
Previous Next Top
- Writer Zones
- Checkout Operation
- Writer OR Zones
- Writer LIST Zones
- Writer AND Zones
- Writer Zone Attributes
Writer Zones
While the reader zone from previous chapters deal with the reader's end of a mel buffer, we now introduce the mel writer zones for exclusive access to the writer's end of the mel buffer. We are using the term "mel buffer" even if the mel variable is not buffered (which is the default of a mel declaration) -- an unbuffered mel is a mel with a buffer of one slot.
Let's create a new task called
Generator
that wants exclusive access to the mel buffer to Generate
and deposit new products. We still have our customary Producer
task that has been generating "mainstream" products. This Generator
task is used to insert some "rogue" products into the production line.
/* Generator - VERSION 1 */
void Generator(<mel> int store) {
try {
<?writer>store {
store = Generate();
}
}
catch ( store<CLOSED> ) {}
}
A writer zone is differentiated from a reader zone by the presence of the mode writer
. While the mode reader
is optional and almost never specified for a reader zone due to its being the default mode, the mode writer
is required to mark the entrance of a writer zone.
Process
The following behind-the-scene process occurs:
- The task puts itself in the mel writers' queue.
- In due course, the task moves up to the top position of the writers' queue. If the mel buffer is all full, the task will wait at this position, until an empty slot in the buffer opens up. Other writer tasks that come up later have to queue behind the task.
- If and when an empty slot is available, the task holds on to this slot, gets into the exclusive writer zone and does its
Generate
work. Note that this exclusivity is for writing only. If the mel buffer has values in the slots at the reading end, consumer tasks can still empty them using the normal mel read access. The task only holds off other writer tasks at the writing end -- which means that other writers must queue behind the task when it is in the writer zone, even if there are available empty mel buffers for them to use.
- When inside the writer zone, the task makes use of the
store
variable. This is an eponymous local variable whose initial value is undefined. The usual goal of a task inside the writer zone is to give a relevant value to the eponymous local variable.
- When the task is done with the writer zone, it either falls out of the writer zone's code at the closing
}
, or invokes thecheckout
operation. The default behavior in both cases is to copy the current value of the eponymous variable into the mel buffer slot that the task is holding. The filled slot is then freed for a reader task.
If the task has not assigned a value to the eponymous variable, the skip behavior takes over. The held mel buffer slot remains empty, and is freed for a new writer task to deposit a value.
- The task removes itself from the mel writers' queue. Any writer task waiting on one of the writers' queues can now vie for the next empty slot.
Let us repeat the conditions that allow the task to get into the writer zone:
- The task must be at the top position of the writers' mel queue
- The mel buffer must have an empty slot for the task to deposit a potential product
Usage
The
Generator
example can be written using a straight mel write instead of a mel write zone:
/* Generator - VERSION 2 */
void Generator(<mel> int store) {
try {
<?>store = Generate();
}
catch ( store<CLOSED> ) {}
}
In VERSION 2, the Generate
functionality is done without holding up the mel store
. Other writer tasks are free to write to the mel store
while Generate
is running. Exclusive access to the writer's end is requested only to transfer the Generate
'd value to the mel variable. In VERSION 1, the Generate
functionality is done inside the exclusive write zone, blocking other writer tasks to write to the mel store
even if there are empty slots to receive their products. Which construct to use depends on the logic of the program.
Checkout Operation
A task can get off a writer zone by either falling off the closing
}
parenthesis or by explicitly invoking the checkout
operation. The following version of the Generator
is logically equivalent to VERSION 1:
/* Generator - VERSION 3 */
void Generator(<mel> int store) {
try {
<?writer>store {
store = Generate();
<checkout>;
}
}
catch ( store<CLOSED> ) {}
}
The checkout
operator, invoked without any attribute, implements the default behavior, which is for the task to copy the value of the eponymous variable into the top slot at the writer's end of the mel buffer, release that slot for a reader task, remove itself from the mel writer's queue and get off the exclusive writer zone.
Skip Behavior
There are times the logic dictates that copying the value of the eponymous variable to the mel slot is not desired. For example, the
Generator
task may not want to write to the mel if the Generate
d value is 0:
/* Generator - VERSION 4 */
void Generator(<mel> int store) {
try {
<?writer>store {
store = Generate();
if ( ! store ) <checkout skip>;
}
}
catch ( store<CLOSED> ) {}
}
The <checkout skip>
causes the task to check out of the writer zone; however the skip
attribute prevents the value of the eponymous variable to be copied into the mel slot. The slot remains empty, and is available for the next writer to fill.
The
skip
behavior can also be used when the eponymous variable is not even present inside the writer zone. Let's introduce a new task called Maintenance
whose purpose is the hold off the production line to do some maintenance:
/* Maintenance - VERSION 1 */
void Maintenance(<mel> int store) {
try {
<?writer>store {
doMaintenance();
<checkout skip>;
}
}
catch ( store<CLOSED> ) {}
}
When Maintenane
is in the writer zone, it locks the mel buffer at the writer's end, preventing any writer task to deposit a new product. These writer tasks can only resume depositing after Maintenance
has checked out.
The
skip
attribute is used here to explicitly indicate that the mel buffer at the writer's end will not be updated. However for Maintenance
, the skip
attribute can be skipped (pun intended) since Maintenance
does not update the eponymous variable store
(it does not even make a reference to it!). When the eponymous variable is not initialized, the default behavior of checkout
is skip
:
/* Maintenance - VERSION 2 */
void Maintenance(<mel> int store) {
try {
<?writer>store {
doMaintenance();
<checkout>;
}
}
catch ( store<CLOSED> ) {}
}
VERSION 2 behaves the same as VERSION 1. It is less verbose than VERSION 1 and also less explicit. Having the skip
attribute visible as in VERSION 1 makes it clear the intended behavior of a checkout operation.
There is a third way to write the above
Maintenance
code. It is to use the mode attribute to specify the skip
behavior as the default behavior.
Eponymous Local Variable
As introduced in Process, the eponymous local variable
store
has the same name as the mel variable store
. Its initial value is undefined. The task then works with this local variable within the writer zone. On exit of the zone, if the eponymous local variable has a bona-fide value, this value will fill the mel buffer slot that the task is holding in the zone. This slot will be available for a reader task to empty. If the eponymous local variable remains undefined, then the task will release the holding empty mel buffer slot without filling it. The next incoming writer task can use that slot to deposit its value.
The eponymous local variable is not the same as the locally cached <value> property. The latter contains the mel value from the last time the task successfully reads from the mel. On the other hand, the eponymous local variable starts out as undefined, and is initialized inside the writer zone. If desired, the programmer can initialize the eponymous local variable with the mel <value> property:
store = store<value>;
Waiting Tasks Wake-Up Order
The previous discussion mentions mel buffers when referring to mel variables. To increase concurrency, a mel variable should be buffered, so that reading can be done on one end of the buffer and writing can be concurrently done at the other end. However by default, a NERWous C mel variable is not buffered; or looking in another way, it has a buffer of one slot.
When the task gets into the writer zone of an unbuffered mel variable, it not only holds off other writer tasks waiting to write, it also holds off any reader task waiting to read due to the mel variable being empty. If the task checks out of the writer zone with a value for the mel variable, the waiting reader task will run first to retrieve the newly deposited value and make the mel variable empty again. The writer task waiting at the top of the writers' queue is run next to deposit a new value. However if the task checks out of the writer zone without depositing a value to the unbuffered mel variable, the writer task at the top of the writers' queue is run first to deposit a value before the waiting reader task can run and empty it.
Writer OR Zones
We have seen reader OR zones. Now let's take a look on writer OR zones. Let's modify
Generator
to make use of two mel variables, and fill the first one that is available.
/* Generator - VERSION 5 */
void Generator(<mel> int store1, <mel> int store2) {
try {
<?writer as=store> (store1 || store2) {
store = GenerateWithSeed(store<value>);
}
}
catch ( store1<CLOSED> && store2<CLOSED> ) {}
}
This new Generator
also makes use of the <value> property. It generates a new value for the chosen mel variable based on the last read value of that mel variable by the task.
Process
This is the behind-the-scene process:
- The task puts itself in
store1
mel writers' queue. If it finds itself at the top position ofstore1
writers' queue and that mel buffer has an empty slot, the task holds on to this slot, gets into the writer zone withstore1
.
- If one of the above conditions for
store1
is not met, the task puts itself instore2
mel writers' queue. If it finds itself at the top position ofstore2
writers' queue and that mel buffer has an empty slot, the task holds on to this slot, gets into the writer zone withstore2
.
- Otherwise, the task will wait on both writers' queues for the writer zone conditions to happen in one of the queues.
- On the first queue that satisfies the conditions (task first on the queue, mel buffer with empty slot), the task holds on to the found slot, removes itself from the other queue, and gets into the writer zone with that mel variable.
- When inside the writer zone, the task makes use of the
store
eponymous local variable, which can represent eitherstore1
orstore2
depending on which mel variable has been picked. The name"store"
comes from the attributeas=store
in the mel writer zone wait construct.
- The task also makes use of
store<value>
which is a totally different entity than the eponymous local variablestore
. The entitystore<value>
is the local <value> property that the task has cached from its last access from the reader's side ofstore1
orstore2
(depending on which mel variable is picked). This value may be undefined if the task has never successfully read from that mel variable. TheGenerator
version above assumes thatGenerateWithSeed
will know how to handle undefined arguments.
- When the task is done (at the closing
}
of the writer zone), if the task has a non-null value in the eponymous local variablestore
, this value is inserted into the mel buffer slot that the task is holding on. IfGenerateWithSeed
returns a null value to the eponymous variable, the eponymous variable is deemed undefined and the task simply releases the hold to the mel buffer slot without updating it. The task remembers which writers' queue it uses, and so removes itself from that queue.
As Attribute
The
as
attribute is usually needed for an OR list so that it can represent the mel variable that is selected on entry of the writer OR zone, as we see in the Generator
example above.
Sometimes, the
as
attribute is not needed. Let's modify the Maintenance
example to use writer OR zone:
/* Maintenance - VERSION 3 */
void Maintenance(<mel> int store1, <mel> int store2) {
try {
<?writer> (store1 || store2) {
doMaintenance();
}
}
catch ( store1<CLOSED> && store2<CLOSED> ) {}
}
Since Maintenance
does not make use of the eponymous local variable, the attribute as
is not necessary in the mel zone wait statement.
Traverse Behavior
The behind-the-scene description above uncovers a subtle difference between
<?writer as=store> (store1 || store2)
and
<?writer as=store> (store2 || store1)
The first mel item in the OR list is checked first. If it is emptied constantly by a reader task, the mel write wait on that first item is likely successful and the writer OR zone is spent more with the first mel than with the second mel.
This issue can be resolved by using the random traverse behavior for OR writes:
/* Generator - VERSION 6 */
void Generator(<mel> int store1, <mel> int store2) {
try {
<?writer as=store> (store1 ||<random> store2) {
store = GenerateWithSeed(store<value>);
}
}
catch ( store1<CLOSED> && store2<CLOSED> ) {}
}
Writer LIST Zones
Unlike the writer OR zone which only requires either specified mel resource, the writer LIST zone allows the programmer to have a task entering a writer zone with all the requested mel resources.
Let us write a new version of
Generator
using a LIST wait, with a comma separated the mel variables in the wait statement:
/* Generator - VERSION 7 */
void Generator(<mel> int store1, <mel> int store2) {
try {
<?writer> (store1, store2) {
store1 = GenerateWithSeed(store1<value>);
store2 = GenerateWithSeed(store2<value>);
}
}
catch ( store1<CLOSED> || store2<CLOSED> ) {}
}
This is the behind-the-scene process:
- The task puts itself in both the
store1
andstore2
mel writers' queues.
- In each queue, whenever the task finds itself at the top position and that mel buffer has an empty slot, the task holds on to this slot, and continues to check for the same write conditions on the other queue.
- When a task holds on to a slot in a queue, it will prevent other writer tasks waiting on that queue to proceed, even if there are empty slots to accomodate those writers. Since it is also waiting on another queue, a deadlock event can occur if multiple writer tasks vie for the same mel resources in a round-robin fashion.
- When the write conditions are also satisfied on the second queue, the task holds on the this slot. The task now holds on to both required slots, it can now check in to the writer zone.
- When inside the writer zone, the task makes use of the
store1
andstore2
eponymous local variables. These local variables start out as undefined, and then initialized with the result of the function call toGenerateWithSeed
.
- The task also makes use of
store1<value>
andstore2<value>
, which are totally different entities than the eponymous local variablestore1
andstore2
. The entitystore1<value>
andstore2<value>
are the local <value> properties that the task has cached from its last access to these mel variables from their reader's sides. These values may be undefined if the task has never successfully read from these mel variables. TheGenerator
version above assumes that GenerateWithSeed will know how to handle undefined arguments.
- When the task is done (at the closing
}
of the writer zone), if the eponymous variable is non-null, its value is deposited into the corresponding empty slot that the task is holding on. IfGenerateWithSeed
returns a null value to the eponymous variable, the eponymous variable is deemed undefined and the task simply releases the hold to the mel buffer slot without updating it.
Writer LIST zones can be checked in and out without touching the eponymous local variables, as seen in this version of
Maintenance
:
/* Maintenance - VERSION 4 */
void Maintenance(<mel> int store1, <mel> int store2) {
try {
<?writer> (store1, store2) { {
doMaintenance();
}
}
catch ( store1<CLOSED> || store2<CLOSED> ) {}
}
The task Maintenance
will enter the writer zone holding on the mel empty slots of both store1
and store2
. Upon exit of the zone, the task simply releases the empty slots it is holding without filling them with a value. Waiting or future writer tasks will fill in these slots. As discussed previously, one possible use of this type of Maintenance
task is to block write access to the mel product lines in order for some maintenance work to take place.
The writer LIST zone should not be used if the programmer has knowledge of round-robin resources requests. These can cause deadlocks. For example, task 1 has hold to mel 1 and waits for mel 2, while task 2 has hold to mel 2 and waits for mel 1. If deadlocks are possible, writer AND zones should be considered.
Writer AND Zones
The writer AND zone should be used when there is a chance of requests for mel resources to cause deadlocks. The requested resources are listed with the
&&
separator:
/* Generator - VERSION 8 */
void Generator(<mel> int store1, <mel> int store2) {
try {
<?writer> (store1 && store2) {{
store1 = GenerateWithSeed(store1<value>);
store2 = GenerateWithSeed(store2<value>);
}
}
catch ( store1<CLOSED> || store2<CLOSED> ) {}
}
The writer AND zone is entered via the mel AND write waits applied to all the requested mel variables (store1
and store2
in the above example). This wait is an all-or-nothing wait. If the Generator
task has the "top-of-the-queue" condition satisfied in one queue, but not at the other queue(s), it will allow other writer tasks at that queue to "jump the line" and put their products to the mel buffer empty slots. When the task belongs to the "top-of-the-queue" groups in all of the mel requests, then it will prevent later-coming writers to "jump the line". This allows the task to have exclusive access to the writer's end of the mel buffers of all the requested mel variables during its stay in the writer exclusive zone.
Inside the writer zone, the task makes use of the eponymous local variables that represent the requested mel variables. The processing on checkout of a writer AND zone is the same as for a writer LIST zone described previously.
Both the reader AND zone and the writer AND zone prevent deadlocks from multiple tasks that vie for the same resources in a circular way. Is the waiting problem more prevalent with the reader AND zone, since there is only one mel value at the top of the reading side of a mel buffer? At least at the writer's end, we can increase the buffer size so that multiple writers can deposit their products. However the waiting to enter a writer AND zone is as dire as waiting to enter a reader AND zone. This wait is not to access the available empty buffer slots (which we can increase), but to the top end of the writer's queue (which there is only one, like the top end of the reader's end queue). Once a task is in the writer end zone, it will block other writer tasks to access the writer's end of the queue, even though there may be empty slots for them to deposit their products.
Writer Zone Attributes
The attributes for a mel writer zone wait is similar to the ones for a mel reader zone:
Attribute | Synopsis |
---|---|
mode | Default mode on checkouts |
priority | Wait to check in using this priority |
timeout | Wait to check in up to this timeout value. If the timeout is reached, a <TIMEOUT> exception will be raised. |
Mode Attribute
The
mode
keyword is usually omitted. The following code snippets are equivalent:
<? mode=writer> store { ... } /* canonical */
<?writer> store { ... } /* popular */
There are two mode attributes for a mel writer zone wait:
Mode Value | Synopsis |
---|---|
writer | As a required value, it differentiates a mel writer zone wait from a mel reader zone wait. As the default behavior, it indicates that the checkout behavior is to update the held-up mel slot with the initialized value of the eponymous local variable. If the eponymous local variable is not defined or initialized, the checkout behavior is the skip behavior. |
writer+skip | This sets the default checkout behavior to be the skip behavior, even if the eponymous local variable is defined and initialized. |
The
Maintenance
example used in introducing the skip behavior on checkout
, indicates that any checkout statement should be invoked with the skip
attribute. Since the checkout behavior is always a skip
, it can be specified on entry of the mel writer zone with the mode
attribute:
/* Maintenance - VERSION 5 */
void Maintenance(<mel> int store) {
try {
<?writer+skip>store {
doMaintenance();
<checkout>;
}
}
catch ( store<CLOSED> ) {}
}
Previous Next Top
No comments:
Post a Comment