class DataOrQuit : public CSProcess { private: AltChanin<Data> dataIn; AltChanin<QuitSignal> quitIn; protected: void run() { try { list<Guard*> guards; guards.push_back(quitIn.inputGuard()); guards.push_back(dataIn.inputGuard()); Alternative alt(guards); QuitSignal qs; Data data; while (true) { switch (alt.priSelect()) { case 0: quitIn >> qs; dataIn.poison(); quitIn.poison(); return; case 1: dataIn >> data; // ... Handle data ... break; } } } catch (PoisonException&) { quitIn.poison(); dataIn.poison(); } } public: DataOrQuit(const AltChanin<Data>& _dataIn,const AltChanin<QuitSignal>& _quitIn) : dataIn(_dataIn),quitIn(_quitIn) { } };
The above points cover most of the semantics of csp::Alternative. It will only ever "choose" one of the available guards, so the typical use is to repeatedly use it in a loop, forever waiting for an input on one of many channels. You must remember to actually perform the input yourself after an input guard has been chosen. Not doing so could result in strange behaviour in your application.
In the example above, we used csp::Alternative::priSelect(), with the quitIn channel as highest priority. Consider what would happen if the dataIn guard had the highest priority instead. If there was a continual fast flow of data on the dataIn channel input, it would always be chosen. Even if the quitIn guard was always ready, if the dataIn guard is also always ready, the quitIn guard will never be chosen. This is referred to as "starvation". In our example, we avoided this by making the quitIn guard (which will be used much less frequently, and only once) have the higher priority. In other circumstances we would probably use the csp::Alternative::fairSelect() method to ensure fair selection between the guards -- priority is rotated between all the guards, so starvation cannot occur. Be careful when using this class. This is a correct use:
void run() { list<Guard*> guards; guards.push_back(in0.inputGuard()); guards.push_back(in1.inputGuard()); Alternative alt(guards); while (true) { switch (alt.fairSelect()) { // ... } }
This, however, is an incorrect use:
void run() { while (true) { list<Guard*> guards; guards.push_back(in0.inputGuard()); guards.push_back(in1.inputGuard()); Alternative alt(guards); // BAD! switch (alt.fairSelect()) { // ... } }
In the latter example, the csp::Alternative is reconstructed each iteration of the while loop. This means the fairSelect will not work as intended -- the priority will not be rotated because each csp::Alternative is actually different from the last. The first example is correct, because the same csp::Alternative object is used on each loop iteration, and thus it can remember where the rotated priority lies.
When one of the channels being used as a guard becomes poisoned, its guard will forever be ready while it is poisoned. A csp::PoisonException will only be thrown when the channel input occurs (usually in the body of a switch statement). The select method will never throw a csp::PoisonException, whether the guard is selected or not selected.
Imagine you want a process that sends a message at least every second -- either a new value, or repeating thel last receieved value:
class OneSecondSender : public CSProcess { private: AltChanin<int> in; Chanout<int> out; protected: void run() { try { Alternative alt( boost::assign::list_of<Guard*> ( in.inputGuard() ) ( new RelTimeoutGuard(Seconds(1)) ) ); int data = 0; while (true) { switch (alt.priSelect()) { case 0: in >> data; out << data; break; case 1: //Send most recent value: out << data; break; } } } catch (PoisonException&) { in.poison(); out.poison(); } } public: // ... Constructor ... };
The main thing to note with timeout guards is that while they behave sensibly (a timeout guard will never be ready before its time is up), they are not hard real-time. If you used the above process and never sent it a value, it would send out values every 1.001 seconds or similar (largely dependent on your OS, and what load it was under at the time). Due to the time taken processing the alt, and in sending the value, a small delay would naturally occur.
Similarly, do not assume that small values will or will not expire -- although a RelTimeoutGuard with exactly zero seconds will always be ready. If you have as your first guard in a priSelect() a RelTimeoutGuard with a small timeout, (say, a few micro- or even, in some cases, milliseconds), it will sometimes be ready "immediately", and sometimes it will not. This depends on the processing time during the alt, and whether the operating system has scheduled the process out at an inopportune moment. You will find that most of the time it is not ready, but do not build your program around false assumptions about the timeout system. It is not exact, but it will work well enough for most purposes.
class StringLoggerProcess { Chanin<std::string> in; Chanout<std::string> out; protected: void run() { try { std::string s; while (true) { ScopedExtInput<std::string> extInput(in,&s); std::cout << "String sent: " << s << std::endl; out << s; } } catch (PoisonException&) { in.poison(); out.poison(); } } // ... constructor ... };
This guide is continued in Guide part 3: Shared Channels and Buffered Channels.