One2OneChannel<int> c,d; { ScopedForking forking; forking.fork(new Id<int>(c.reader(),d.writer())); c.writer() << 6; int n; d.reader() >> n; c.writer().poison(); d.reader().poison(); }
It is very important that the channels used by the forked process were declared before the csp::ScopedForking. In C++, objects are destroyed in reverse order of their construction. So if the channels were constructed after the csp::ScopedForking, they would be destroyed before csp::ScopedForking. This means that the processes you have forked could end up using ends of channels that have been destroyed. So it is very important that you declare all channels, barriers, buckets, etc that the forked processes use before the csp::ScopedForking.
You should also be careful about poisoning channels and such. Consider this illustrative code:
One2OneChannel<int> c,d,e; try { ScopedForking forking; forking.fork(new Successor<int>(c.reader(),d.writer())); forking.fork(new Widget(e.writer())); int n; e.reader() >> n; n *= -6; c.writer() << n; d.reader() >> n; } catch (PoisonException& e) { e.reader().poison(); c.writer().poison(); d.reader().poison(); }
Consider what would happen if the Widget process poisoned its channel "e" before we tried to read from it. A csp::PoisonException would be thrown. As part of the stack unwinding, the csp::ScopedForking object would be destroyed -- which would cause it to wait for all its forked processes. However, the Successor process would not have finished, because it runs until it is poisoned -- and we will not poison its channels until the catch block. So the above code will deadlock if the Widget channel turns out to be poisoned. You can avoid such problems by declaring the csp::ScopedForking object outside the try block (but after the channel declarations).
csp::ScopedForking can be used to fork compounds of processes -- anything that can be passed to the Run command can be passed to csp::ScopedForking::fork:
forking.fork( InParallel(P)(Q) ); forking.fork( InSequence(S)(T) );
forking.fork( InParallel(P)(Q) ); forking.fork( InSequence(S)(T) );
The alternative to kernel-threads is user-threads. User-threads live inside kernel-threads -- two user-threads in the same kernel-thread cannot run on separate processors in parallel. However, communication between user-threads is usually five to ten times as fast as kernel-threads. Processes can be grouped together as follows:
forking.fork( InParallelOneThread(P)(Q) );
forking.forkInThisThread( InParallelOneThread(P)(Q) );
forking.forkInThisThread( InParallel(P)(Q) );
More detail on the threading arrangements that different combinations give are available on the Running Processes page.
This guide is continued in Guide part 5: Mobiles, Barriers and Buckets.