Monday 20 August 2007

Example (5): Concurrency

This post is to say there is always some means if we know clearly what a trouble is.

Let's reproduce Gary's example:
Bidder1 -> Auctioneer: integer;
Bidder2 -> Auctioneer: integer;
By our Rule of Thumb (see Example (2)) we can read the above signature thus:
Bidder1 and Bidder2 will send their prices asynchronously, while Auctioner expects the price from Bidder1 first and the price from Bidder2 next.
But we ask: can this be possible? For example suppose Bidder1 sends £1,000 and Bidder2 sends £2,000: it is important that Auctioneer knows which is which. But since messages are asynchronous, they should travel over the vast expanse of networks, those bids may not arrive in the order Auctioneer want.

So some means is needed: well in fact there are several means.
  • Use the "reordering scheme" and distinguish two messages by their content, say some field: for example we may include "sender" field.
  • Ditto but this time distinguish mesages using different signatures --- that is eitherdifferent message types and/or different operator names.
  • Do not use "reordering scheme" but use distinct channels: Auctioneer now owns two distinct channels, one for Bidder1 and another for Bidder2 and each of them send to distinct channels.
The first option may seem to work but in fact it is a reasonably bad idea: for example even the same sender may send two messages from its two distinct threads. Moreover the runtime at a client-side should go deep into a message content to check whether it is a correct message or not. And further suppose you wish to encrypt your message via an end-to-end means.

The second one is a feasible idea: while it is not as robust as the third one (channels) it can surely be usable in some situations and perhaps we do not wish to prohibit it. So for example we can write:
Bidder1 -> Auctioneer: BiddingMessage1;
Bidder2 -> Auctioneer: BiddingMessage2;
Or alternatively we can use:
Bidder1 -> Auctioneer: bidding1(integer);
Bidder2 -> Auctioneer: Bidding2(integer);
where we assume "bidding1" part is an operator, like a method in objects: the idea of signature allows us to go between message types and operators seamlessly.

However this approach, while not too bad, can have a problem for example in the following situation:
parallel {
   Alice -> Bob: hello();
   Bob -> Alice: hereiscake(Cake);
   Alice -> Bob: thankyou();
} and {
   choice {
      Alce -> Bob: hello();
      Bob -> Alice: hello();
   } or {
      Alice -> Bob: goodbye()
      Bob -> Alice: goodbye();
   }
}
This is a complex situation: in one conversation Alice and Bob exchange greetings and Bob offer a piece of cake to Alice: on the other Alice and Bob can be engaged in a slightly more sublte dialogue. Since we may wish to edit each conversation separately (and we may add other parallel conversations as we like) it may be a good idea to make these distinct conversations to be targetted to distinct channels.

A simple exchange such as:
Alice -> Bob: hello(Letter);
Bob -> Alice: hello(Letter);
can be considered as using default channels at each participants. It is very natural to consider Alice has one or more channels at which she is waiting to receive messages --- but belonging to that conversation. For simpler conversations you use only one channel, but if you have many conversations going on in parallel, its' good to distinguish these threads using separate channels.

From a theoretical viewpoint we are saying channels are used as units to maintain causality chains --- subconversations on distinct channels can be considered, in principle, as causally unrelated: so sequencing across channels does not matter, conversations can be monitored independently across channels, and you have reliable and highly effective basis to do various optimisations. We shall be touching each of these points in our subsequent posts.

When we use channels, there is a natural notation for interaction. We shall introduce them as well as further motivating the introduction of channels through examples.