Code developed for the old 1.x versions of C++CSP will not compile or function the same with C++CSP2. I realise that this is very annoying for those who have already developed programs for C++CSP v1.x, and now have to migrate them. The reasons for this lack of compatibility include:
To help all previous users of C++CSP v1.x to convert their programs for use with C++CSP2, I have prepared this guide. I will go through the various areas of C++CSP2 that have changed. Thankfully, almost all of these changes will result in a compile error. So if you go through each compile error, then with the help of this guide you can convert your program to using C++CSP2 properly. Code blocks are provided that show the OLD code, and the corresponding NEW code.
#include <csp/csp.h> //OLD #include <cppcsp/cppcsp.h> //NEW
The functions used to start and stop the C++CSP2 library have had their names changed (for similar reasons to the above):
Start_CSP(); //OLD Start_CSP_NET(); //OLD End_CSP(); //OLD Start_CPPCSP(); //NEW End_CPPCSP(); //NEW
The sleep/wait functions used to be members of csp::CSProcess. This caused annoyance if you wanted to call these functions in a class that was not itself a process. So these functions have now been moved to being stand-alone:
class SomeProcess : public CSProcess { protected: void run() { sleepFor(Seconds(1)); //OLD, sleepFor was a member of CSProcess SleepFor(Seconds(1)); //NEW, SleepFor is a function in namespace csp Time t; CurrentTime(&t); t += Seconds(1); sleepTo(t); //OLD, sleepTo is a member of CSProcess SleepUntil(t); //NEW, SleepUntil is a function in namespace csp } };
Similarly, yield used to be a member function of csp::CSProcess. There are now two stand-alone yield functions in C++CSP2; csp::CPPCSP_Yield() yields to other user-threads in the same thread, and csp::Thread_Yield() yields to other kernel-threads. CPPCSP_Yield is useful if you are not doing any channel communications or synchronisation and want to allow other user-threads to run. Thread_Yield is perhaps less useful; your thread will be switched out at the end of its time-slice anyway.
There are now two process classes; csp::CSProcess and csp::ThreadCSProcess. Usually you will want to use CSProcess as before; if you do not change any of your existing classes from inheriting from CSProcess, everything will work fine as before. For an explanation of the purpose of csp::ThreadCSProcess, click on its link to read the documentation.
The constructor of csp::CSProcess used to have two parameters, one which was a mandatory stack size and one which was a mysterious reserve size. This constructor now takes one optional argument (the stack size). The reason for it becoming optional is not that I have added a magical way of determining the right stack size (alas!), but because there is an arbitrary high default value of 1 megabyte. All your existing processes that specify a stack size should be fine as before -- although if you are moving to 64-bit, remember that some of your data types will double in size. You can no longer specify a reserve size -- if you have any code that did specify the second parameter (which is unlikely), simply remove the second parameter.
One of the major changes to the library has been in the area of running processes. The new mechanism is explained in detail on the Running Processes page. Below are examples of how to convert your existing code to the new system. It can either be converted as-is, to continue using user-threads, or you can change to using kernel-threads:
CSProcess* processA = new Widget; CSProcess* processB = new OtherWidget; Parallel(processA,processB,NULL); //OLD RunInThisThread(InParallelOneThread (processA) (processB) ); //NEW, using user-threads //or: Run(InParallelOneThread (processA) (processB) ); //NEW, using kernel-threads (processA and processB together in a single new thread) //or: Run(InParallel (processA) (processB) ); //NEW, using kernel-threads (processA and processB together in separate new threads) Sequential(processA,processB,NULL); //OLD RunInThisThread(InSequenceOneThread (processA) (processB) ); //NEW, using user-threads //or: Run(InSequenceOneThread (processA) (processB) ); //NEW, using kernel-threads (processA and processB together in a single new thread) //or: Run(InSequence (processA) (processB) ); //NEW, using kernel-threads (processA and processB together in separate new threads)
You will notice that the syntax has changed. Gone is the annoying C-style NULL-terminated vararg list, replaced by a new C++ mechanism. Each parameter is bracketed separately -- no commas! There is a new csp::Run method that can take sequential or parallel lists. You can also easily compose parallel sequences or sequenced parallels. All the detail is on the Running Processes page.
The mechanism for forking has also changed. The old mechanism involved spawning a process with a given barrier to synchronise on when it was done. The new mechanism uses the Scoped Classes idea.
CSProcess* processA = new Widget; //begin OLD Barrier barrier; spawnProcess(processA,&barrier); //spawnProcess was a static method of CSProcess //... Do something ... barrier.sync(); //wait for processA to finish //end OLD //begin NEW { ScopedForking forking; forking.forkInThisThread(processA); //using user-threads, processA remains in this thread //or: forking.fork(processA); //using kernel-threads, processA is in a new thread //... Do something ... } //When forking goes out of scope, it waits for all the forked processes to finish //end NEW
The new csp::ScopedForking class is a little different, but ultimately it is easier and nicer than the old mechanism. This way you can't forget to synchronise on the barrier and wait for the processes, even if an exception is thrown.
C++CSP v1.x had a single channel input-end type; Chanin. Unbuffered unshared channels supported all its functions, but buffered channels did not support extended input and channels with shared reading ends did not support alting. If you tried to use unsupported functionality, a FunctionNotSupportedException was thrown. This has now been mixed through two major changes:
If you use a channel-end in an alt, you will need to change the type from Chanin to AltChanin. You will get compile errors otherwise -- complaining about the lack of an inputGuard() method (although this method itself has now changed -- see the section on Alting below).
The changes made to C++CSP to support kernel-threads in C++CSP2 meant that this system could no longer be retained. Although this does help make the library similar, it will make simple parallel communications more cumbersome. The best way to mimic the functionality is to use the csp::common::WriteOnceProcess and csp::common::ReadOnceProcess processes:
//in and out are Chanin<int> and Chanout<int> respectively int inInt,outInt; outInt = 9; //begin OLD: ParallelComm pc(in.parIn(&inInt),out.parOut(&outInt)); pc.communicate(); //end OLD //begin NEW Run(InParallelThisThread (new ReadOnceProcess<int>(in,&inInt)) (new WriteOnceProcess<int>(out,&outInt)) ); in.checkPoison(); out.checkPoison(); //end NEW
The checkPoison() calls are important. If the old ParallelComm encountered poison, it would throw a PoisonException. Running the sub-processes as above will not detect poison; if the processes do attempt to use poisoned channels, they will fail and stop. To find out if the channels are now poisoned, you must check the poison manually. Even then, you will not know using the above code whether or not the channel communications succeeded before the poison or not.
There are other ways of implementing the same functionality as ParallelComm. One option would be to perform the communications sequentially. This would make things much easier, but it is not always possible (this could lead to deadlock). Another option, if you do not mind some buffering, is to use buffered channels sequentially, or to use a csp::common::Id process to achieve a similar effect.
One2OneChannel<int> c; Chanin<int> in = c.noPoisonReader(); //OLD Chanin<int> in = NoPoison(c.reader()); //NEW
The names of the buffered channels have changed, from One2OneChannelX to BufferedOne2OneChannel. The original names were actually the result of a mix-up but were never changed, until now. Other changes to the buffered channels are detailed in the Buffered Channels section below.
The names of the channel buffers have always changed. Previously - in line with JCSP - the channel buffers were derived from the ChannelDataStore class. Copies of buffers were obtained by calling the clone() method of the buffer.
In C++CSP2, channel buffers now derive from csp::ChannelBuffer. The interface has changed to be more flexible and support new features - this is only of concern if you previously implemented your own buffers. Instead of a clone() method, copies of buffers are obtained from a channel-buffer factory.
//begin OLD: Buffer<int> buffer; One2OneChannelX c(buffer),d(buffer); //end OLD //begin NEW: FIFOBuffer<int>::Factory bufferFactory; BufferedOne2OneChannel c(bufferFactory), d(bufferFactory); //end OLD
You will note that the buffer names have also changed. Buffer has become csp::FIFOBuffer and InfiniteBuffer is now csp::InfiniteFIFOBuffer. ZeroBuffer has gone (use an unbuffered channel instead) and csp::OverwritingBuffer has been added.
The other major change to buffered channels is that extended input on buffered channels is now supported. The behaviour is roughly intuitive - in the non-overwriting FIFO buffers, the item of data is read from the buffer at the beginning of the extended input but not removed until the end of the extended input. However for the overwriting buffer (to prevent the writer ever blocking), the data is effectively removed at the beginning of the extended input.
//begin OLD: class MyProcess { Chanin<int> in; Chanout<int> out; int n; public: void extendedAction() { out << n; } protected: void run() { while(true) { in.extInput(&n,this,&MyProcess::extendedAction); } } }; //end OLD //begin NEW: class MyProcess { Chanin<int> in; Chanout<int> out; protected: void run() { while(true) { int n; { ScopedExtInput<int> extInput(in,&n); out << n; } //extended input finishes at end of scope } } }; //end NEW
Anyone who had to use the old method will appreciate the comparative ease of this new version. No more awkward member functions or static functions; the code is inline where you would want it to be. This method is also exception-safe; in the case of an exception being thrown during an extended input (for example, poison), the extended input is still ended safely. More details are available on the csp::ScopedExtInput page.
Barrier-ends (csp::BarrierEnd) are always "encased" in a csp::Mobile wrapper:
//begin OLD: Barrier barrier; barrier.enroll(); barrier.sync(); barrier.resign(); //end OLD //begin NEW: Barrier barrier; Mobile<BarrierEnd> barrierEnd(barrier.end()); barrierEnd.enroll(); barrierEnd.sync(); barrierEnd.resign(); //end NEW
Processes that used to take a Barrier* parameter to their constructor should now take a Mobile<BarrierEnd> parameter.
I have now reversed the situation in C++CSP2, returning to the JCSP method. I realise that this will be very confusing to existing C++CSP v1.x users, but I think this tough transition is for the best. It will be less confusing for new users and allows extended inputs to be done in a nice way. Existing alting code will break because the inputGuard method no longer takes a parameter, so you will be able to see where the code needs changing. You must removed the parameter, and instead manually perform the input inside the switch statement.
For example:
//begin OLD: class MyProcess { Chanin<int> in0,in1; Chanout<int> out; int n0,n1; public: static void extInput(CSProcess* proc) { MyProcess* myproc = (MyProcess*)proc; myproc->out << myproc->n1; } protected: void run() { Alternative alt( in0.inputGuard(&n0), in1.extInputGuard(&extInput,&n1), new RelTimeoutGuard(csp::Seconds(1)), NULL); while (true) { switch (alt.priSelect()) { case 0: //no need to perform input out << n0; break; case 1: //no need to perform input //The real code was in the extended action break; case 2: //timeout out << -1; break; } } } }; //end OLD //begin NEW: class MyProcess { AltChanin<int> in0,in1; //Now AltChanin instead of Chanin Chanout<int> out; protected: void run() { int n; Alternative alt( boost::assign::list_of<Guard*> (in0.inputGuard()) (in1.inputGuard()) (new RelTimeoutGuard(csp::Seconds(1))) ); while (true) { switch (alt.priSelect()) { case 0: //must perform input: in0 >> n; out << n; break; case 1: //must perform extended input: { ScopedExtInput<int> extInput(in1,&n); out << n; } break; case 2: //timeout out << -1; break; } } } }; //end NEW
You will notice a few of the other changes; AltChanin must be used for alting channel-ends, instead of Chanin. The guard list that is passed to the Alternative is no longer the clumsy NULL-terminated vararg list, but instead uses the cleaner boost::assign library. Guards for extended inputs are now identical to normal input guards - you simply choose in the switch statement whether to perform an input or an extended input. You can even vary it each time you perform an alt, if you wanted. As before, the Alternative takes care of deleting the guards itself.