Inter Process Communication: Mailboxes

A mailbox is a mechanism for Inter Process Communication(IPC).

Conceptually mailboxes operate like real mailboxes. A process may put a letter (which in this case is a data message) for another process in a mailbox. The message stays in the mailbox until the receiving process comes and retrieves it. If the receiving process checks the mailbox before the sending process puts the message in the mailbox, the receiving process either waits for the message or retrieves the message on subsequent trips to the mailbox.

A message in this case can be any singular type (i.e any variable other than an unpacked array, structure or union type). A non-parameterized mailbox can accept and retain messages of any singular type.

The only other restriction is that a message must be a valid left-hand side expression. For example, a net type, such as a wire, is not a valid message. So, you can use a single mailbox to handle various types of messages(from a single or multiple source).

Also, a mailbox can be bounded, meaning it can hold only a pre-specified maximum number of  messages. Or it can be unbounded, when it can hold all the messages that it receives.

Syntactically, mailbox is a built-in class that can be defined as






Non-parameterized mailboxes are typeless, that is, single mailbox can send and receive different types of data. Thus, in addition to the data being sent(i.e the message queue), a mailbox implementation must maintain the message data type placed by put( ). This is required in order to enable the run-time type checking.

Built-in methods in Mailbox


The class mailbox provides 8 methods that can be used for accessing a mailbox.
  1. new( ) : Create a mailbox with specified number of slots.
  2. num( ) : Find the number of messages in the mailbox
  3. get( ) : Retrieve a message, if available, from the mailbox. Block otherwise.
  4. try_get( ) : Retrieve a message from the mailbox without blocking.
  5. peek( ) : Copy a message, if available, from the mailbox. Block otherwise.
  6. try_peek( ) : Copy a message from the mailbox without blocking.
  7. put( ) : Put a message in the mailbox.
  8. try_put( ) : Put a message in the mailbox without blocking.

new( ):
The function new( ) is a constructor method for the class mailbox. 

The prototype declaration for new( ) is as shown



  • It takes one integer argument bound that describes the number of messages that this mailbox can hold.
  • If bound is 0, the mailbox can hold infinite number of messages. 
  • The default value of this argument is 0.


num( )
num ( ) returns the number of messages in a mailbox.

The prototype of num( ) is as follows:



  • This number changes whenever a process puts or retrieves a message in a mailbox.
  • num( ),  similar to new( ) is a function and hence non-blocking. 
  • It returns the number of messages in the mailbox and takes no argument.
  • The returned value should be used with care because it is valid only until the next get( ) or put( ) is executed on the mailbox.

get( )
The method get( ) retrieves a message, if available, from a mailbox.

The prototype declaration is as shown:



  • If a message is not available, a call to get( ) blocks. 
  • Also, since a message can be any singular type, if the actual type of the message retrieved does not match with the code that calls get( ), there will be a run-time error.
  • Note that a mailbox is a FIFO - the first message that's put in will be popped first.

try_get( )
This method try_get( ) is similar to get( ), except that it is non-blocking.

The prototype for try_get( ) is as shown:



  • If a message is available, it will retrieve the message (and will return 1).
  • If a message is not available, it will return 0 and, 
  • If a message of wrong type is available (indicating a possible programming error), it will return -1.

peek( )
Many times, it is useful to look into a message but without yanking it from the mailbox. The peek( ) method provides such a facility. It lets you copy a message from the mailbox, without actually deleting it from the mailbox.

The prototype of peek( )  is shown below:



  • If there is no message in the mailbox, the behavior of peek( )  is identical to get( ) - it blocks until a message is available in the mailbox.

try_peek( )
The try_peek( ) method is to peek( ), what try_get( ) is to get( ) - it copies a message from a mailbox, if one is available. However, if a message is not available, it does not block.

The prototype for try_peek( ) is shown below:



  • The try_peek( ) method returns 
    • 1, if a message is available, 
    • 0, if a message is not available and 
    • -1, if a message of proper type is not available.

put( ) 
A process uses the put( ) method to place a message in a mailbox.

The prototype for put( ) method is shown below:



  • This method is a task and if there are not enough places to put the message in a bounded mailbox, the call will block (i.e wait until a place is available).

try_put( )
This method try_put( ) is very similar to put( ), except that it does not block if there is no place available in the mailbox.

The prototype for try_put( ) method is shown below:



Following is an example for mailbox and the usage of its built-in methods.








































Parameterized Mailboxes
General mailboxes can hold any type of message. As long as a message matches with the expected type during retrieval, everything works seamlessly.

If it is known that a mailbox will be used with only one type of message, say string type, the mailbox definition can be parameterized. This is shown below.









The usefullness of paramterized mailboxes is that in a case of a type mismatch (as in the incorrect example above), it will be a compilation error, not a runtime error.




Thread Synchronization Using Mailboxes
Producer-Consumer without synchronization




































The producer runs to completion before the consumer even starts. This is because a thread continues running until there is a blocking statement, and the Producer has none. The Consumer thread blocks on the first call to mbx.get.

Output:
Producer: before put(1)
Producer: before put(2)
Producer: before put(3)
Consumer: after get(1)
Consumer: after get(2)
Consumer: after get(3)

Synchronization can be achieved as follows:
the Producer creates and puts a transaction into a mailbox, and then blocks until the Consumer finishes with it. This is done by having the Consumer remove the transaction from the mailbox only when it is finally done with it, not when the transaction is first detected.




Producer-Consumer synchronized with bounded mailbox
The Consumer uses the built-in mailbox method peek( ) to look at the data in the mailbox without removing. When the Consumer is done processing the data, it removed the data with get( ). This frees up the Producer to generate a new value.

If the Consumer loop started with a get( ) instead of the peek( ), the transaction would be immediately removed from the mailbox, and so the Producer could wake up before the Consumer finished with the transaction.






































Output:
Producer: before put(1)

Producer: before put(2)
Consumer: after get(1)
Consumer: after get(2)
Producer: before put(3)
Consumer: after get(3)

The Producer and Consumer are in lock-step, but the Producer is still one transaction ahead of the Consumer. This is because a bounded mailbox with size=1 only blocks when you try to do a put of the second transaction.



Producer-Consumer synchronized with a mailbox and event
The two threads use a handshake so that the Producer does not get ahead of the Consumer. The Consumer already blocks, waiting for the Producer using a mailbox. The Producer needs to block, waiting for the Consumer to finish the transaction. This is done by adding a blocking statement to the Producer such as an event, a semaphore, or a second mailbox.







































Output:
Producer: before put(1)
Consumer: after get(1)
Producer: after put(1)
Producer: before put(2)
Consumer: after get(2)
Producer: after put(2)
Producer: before put(3)
Consumer: after get(3)
Producer: after put(3)



Producer-Consumer synchronized by using two mailboxes
To synchronize the two thread use a second mailbox that sends a completion message from the Consumer back to the Producer.

The return message in the rtn mailbox is just a negative version of the original integer. 








































Output:
Producer: before put(1)
Consumer: before get
Consumer: after get(1)
Consumer: before get
Producer: after get(-1)
Producer: before put(2)
Consumer: after get(2)
Consumer: before get
Producer: after get(-2)
Producer: before put(3)
Consumer: after get(3)
Producer: after get(-3)

No comments:

Post a Comment