Running Processes


Detailed Description

A Brief Introduction to Threading

Underneath C++CSP2, there is a threading system that uses both kernel-threads and user-threads. User-threads live inside kernel-threads; that is, kernel-threads can contain multiple user-threads. This may not mean much to programmers who are not familiar with threading, but there are four important points:

  1. Two kernel-threads can execute in parallel on two different CPUs (or cores), but two user-level threads inside the same kernel-thread can never execute in parallel on different CPUs or cores.
  2. A user-thread will only switch to a sibling user-thread when it performs a channel communication, uses some other C++CSP2 primitive (barriers, buckets, etc), or makes an explicit CPPCSP_Yield() call.
  3. If a user-thread makes a function call to the operating system, its sibling user-threads will be unable to execute until the call has returned. For example, if one user-thread reads from a socket, its sibling user-threads will not be able to do anything until this socket-read has returned.
  4. User-threads are much faster and usually more scalable than kernel-threads. With simple communication-bound processes, programs based on user-threads are usually around ten times faster than the same program using kernel-threads. This is only for communication of course - the actual processing code inside the process will run at the same speed regardless, but the communications between processes are faster between user-threads.

Because kernel-threads are easier to understand C++CSP2 uses kernel-level threads by default (especially because of point 3 above) and because it fits the user's intuition that running parallel processes on a multi-core machine will take advantage of the multiple cores (see point 1 above).

The reason for providing user-threads at all is the last point on the list (point 4). Where performance or scalability is an issue, user-threads provide a way to improve performance over kernel-threading. So users who want to run simulations with tens or hundreds of thousands of processes will want to use a mixture of user-threads and kernel-threads, and will also want to read the section on stack sizes on the Processes page.

Wherever you see thread used on this page without a kernel- or user- qualifier, it means kernel-thread.

Running Processes

Processes can be run in two main ways in C++CSP2; using a Run() call or using forking (with the ScopedForking class). The Run() calls wait until all the sub-processes have finished before returning, whereas forking is used when you wish to start a new process, but continue running the current process alongside.

Both methods delete the process when the process has finished. Processes should always be allocated using the new operator, never on the stack. You should not delete processes yourself, and you should never attempt to run the same process twice (neither sequentially nor in parallel).

If not enough resources (either memory or OS resources) are available to create a thread, an OutOfResourcesException will be thrown. If you have made a call to spawn many processes, then the processes that have already started will stay running, but the processes that have not yet been run will be deleted. After the first failure, no further attempt is made to run any of the other processes.

If this error does occur, you will probably be left with half of your process network running, and half of it unstarted, which will probably cause your program to deadlock. You should probably attempt to poison all channels. This error is unlikely to occur unless you are running a very large program; common Windows and Linux machines should allow over a thousand processes (kernel-thread or user-thread) to be run in parallel without a problem.

Processes can be easily composed, either in parallel or in sequence. The following sections contain many examples of the use of the Run() function, and shows the result of each. All the processN variables are assumed to be of type CSProcess* unless specifically noted.

Simple Parallel Processes

        Run(process0);
The above line runs process0 in a new kernel-thread, only returns when process0 has finished:

        RunInThisThread(process0);
The above line runs process0 in a new user-thread within the current kernel-thread, only returns when process0 has finished:

        Run( InParallel (process0) (process1) );
        Run( InParallel(process0).with(process1) );
        std::list<CSProcess*> processes;
        process0.push_back(process0);
        process1.push_back(process1);
        Run( InParallel( processes.begin() , processes.end() ) );
All of the above code samples have the same effect. Some programmers may feel uneasy about the syntax of the first sample (which is inspired by the boost::assign library), so they may wish to use the second, more explicit sample instead, or the third lengthier sample that builds a list of processes first. The effect is:

If you instead wanted to put process0 and process1 in the same new thread, you could do the following:

        Run( InParallelOneThread(process0) (process1) );
The above line would give this arrangement:

If you instead want to put both process0 and process1 into the current thread, you could do the following:

        RunInThisThread( InParallelOneThread(process0) (process1) );
The above line would provide this:

Inquiring minds might wonder as to the effect of:

        RunInThisThread( InParallel (process0) (process1) );
The answer is that the above line will not compile.

Simple Sequential Processes

InSequence can be used instead of InParallel with identical syntax:

        Run( InSequence (process0) (process1) );
        Run( InSequence(process0).with(process1) );
        std::list<CSProcess*> processes;
        process0.push_back(process0);
        process1.push_back(process1);
        Run( InSequence( processes.begin() , processes.end() ) );
The effect of the above three samples is identical:

Of course, process1 will not actually be started until process0 has finished - that is the point of composing processes in sequence. The threading arrangements that result from InSequence will be identical to InParallel (and InSequenceOneThread identical to InParallelOneThread), with this added restriction that each process will only be created when its immediate predecessor has finished.

Complex Composite Processes

Running processes directly in parallel or in sequence is simple, as seen above. Sometimes however you will want to nest parallel and sequentially arranged processes; you may want to run (process0 then process1) in parallel with process2 and (process3 then (process4 in parallel with process5)):
        Run( InParallel
            ( InSequence (process0) (process1) )
            ( process2 )
            ( InSequence
                ( process3 )
                ( InParallel (process4) (process5) )
            )
        );
The threading arrangement of the above line will be (the numbering of the threads is arbitrary, and does not represent the order they will be started in):

You may wish to compress this threading arrangement. For example, you may want to put process0 in the same thread as process1, and process 4 and process5 in the same thread as each other. This would be accomplished as follows:

        Run( InParallel
            ( InSequenceOneThread (process0) (process1) )
            ( process2 )
            ( InSequence
                ( process3 )
                ( InParallelOneThread (process4) (process5) )
            )
        );
This would give:

Now consider if you wanted to put process0, process1 and process2 into one thread, and process3, process4 and process5 into another. That could be done as follows:

        Run( InParallel
            ( InParallelOneThread
                ( InSequenceOneThread (process0) (process1) )
                ( process2 )
            )
            ( InSequenceOneThread
                ( process3 )
                ( InParallelOneThread (process4) (process5) )
            )
        );

For completeness, the effect of this:

        Run( InParallel
            ( InParallelOneThread
                ( InSequence (process0) (process1) )
                ( process2A )
                ( process2B )
            )
            ( InSequenceOneThread
                ( process3A )
                ( process3B )
                ( InParallel (process4) (process5) )
            )
        );
will be the following:

All the four arrangements (InParallel, InSequence, InParallelOneThread, InSequenceOneThread ) are associative with themselves. That is, InParallel(x)(InParallel(y)(z)) produces the same threading arrangement as InParallel(x)(y)(InParallel(z)) and InParallel(x)(y)(z) (even taking into account the hidden helper processes). If you nest an InSequenceOneThread directly inside an InParallelOneThread (or vice versa) then the processes in both will be in the same thread.

ThreadCSProcess vs CSProcess (aka "Argh! Why won't it compile?")

One problem with the run system is that it can make compilation errors slightly hard to understand. Most of the problems come down to one central issue. The RunInThisThread(), ScopedForking.forkInThisThread(), InParallelOneThread() and In SequenceOneThread() functions cannot be used on threads (for obvious reasons - something that will get run in a new thread cannot be forced to run in this thread). This means you cannot pass them a helper from InParallel() or InSequence() and nor can you pass them a ThreadCSProcess (except for children of CSProcess).

In most cases, processes should be descended from CSProcess not ThreadCSProcess. But in case you use one that is descended from ThreadCSProcess, you should bear in mind that you cannot use them with one of the calls listed above. Similarly, you cannot nest an InParallel() or InSequence() directly inside an InParallelOneThread() or InSequenceOneThread(). You can however, achieve your desired effect by using the helper's process() call.


Classes

class  csp::ParallelHelper
 A helper class returned by the InParallel() function. More...
class  csp::ParallelHelperOneThread
 A helper class returned by the InParallelOneThread() function. More...
class  csp::RunHelper
 The base class for the various helper classes for the Run function. More...
class  csp::ScopedForking
 A class used to fork processes. More...
class  csp::SequentialHelper
 A helper class returned by the InSequence() function. More...
class  csp::SequentialHelperOneThread
 A helper class returned by the InSequenceOneThread() function. More...

Functions

template<typename ITERATOR>
ParallelHelper csp::InParallel (ITERATOR begin, ITERATOR end)
 Starts an InParallel chain with the given processes.
ParallelHelper csp::InParallel (const ParallelHelper &p)
 Starts an InParallel chain with the given helper.
ParallelHelper csp::InParallel (ThreadCSProcessPtr p)
 Starts an InParallel chain with the given process.
template<typename ITERATOR>
ParallelHelperOneThread csp::InParallelOneThread (ITERATOR begin, ITERATOR end)
 Starts an InParallelOneThread chain with the given processes.
ParallelHelperOneThread csp::InParallelOneThread (CSProcessPtr p)
 Starts an InParallelOneThread chain with the given process.
template<typename ITERATOR>
SequentialHelper csp::InSequence (ITERATOR begin, ITERATOR end)
 Starts an InSequence chain with the given processes.
SequentialHelper csp::InSequence (ThreadCSProcessPtr p)
 Starts an InSequence chain with the given process.
template<typename ITERATOR>
SequentialHelperOneThread csp::InSequenceOneThread (ITERATOR begin, ITERATOR end)
 Starts an InSequenceOneThread chain with the given processes.
SequentialHelperOneThread csp::InSequenceOneThread (CSProcessPtr p)
 Starts an InSequenceOneThread chain with the given process.
void csp::Run (ThreadCSProcessPtr)
 Runs the process in a new thread.
void csp::Run (const SequentialHelperOneThread &)
 Runs the processes sequentially in one new thread.
void csp::Run (const SequentialHelper &)
 Runs the processes sequentially in new threads.
void csp::Run (const ParallelHelperOneThread &)
 Runs the parallel processes in one new thread.
void csp::Run (const ParallelHelper &)
 Runs the parallel processes in new threads.
void csp::RunInThisThread (const SequentialHelperOneThread &)
 Runs the processes sequentially in this thread.
void csp::RunInThisThread (const ParallelHelperOneThread &)
 Runs the parallel processes in this thread.
void csp::RunInThisThread (CSProcessPtr)
 Runs the process in this thread.


Function Documentation

void csp::Run ( const ParallelHelper  ) 

Runs the parallel processes in new threads.

This function is usually called thus: Run(InParallel(x)(y)(z));

void csp::Run ( const ParallelHelperOneThread  ) 

Runs the parallel processes in one new thread.

This function is usually called thus: Run(InParallelOneThread(x)(y)(z));

void csp::Run ( const SequentialHelper  ) 

Runs the processes sequentially in new threads.

This function is usually called thus: Run(InSequence(x)(y)(z));

void csp::Run ( const SequentialHelperOneThread  ) 

Runs the processes sequentially in one new thread.

This function is usually called thus: Run(InSequenceOneThread(x)(y)(z));

void csp::Run ( ThreadCSProcessPtr   ) 

Runs the process in a new thread.

void csp::RunInThisThread ( CSProcessPtr   ) 

Runs the process in this thread.

void csp::RunInThisThread ( const ParallelHelperOneThread  ) 

Runs the parallel processes in this thread.

This function is usually called thus: RunInThisThread(InParallelOneThread(x)(y)(z));

void csp::RunInThisThread ( const SequentialHelperOneThread  ) 

Runs the processes sequentially in this thread.

This function is usually called thus: RunInThisThread(InSequenceOneThread(x)(y)(z));

ParallelHelper csp::InParallel ( ThreadCSProcessPtr  p  )  [inline]

Starts an InParallel chain with the given process.

See the top of this page for information on how to use this function.

ParallelHelper csp::InParallel ( const ParallelHelper &  p  )  [inline]

Starts an InParallel chain with the given helper.

See the top of this page for information on how to use this function.

template<typename ITERATOR>
ParallelHelper csp::InParallel ( ITERATOR  begin,
ITERATOR  end 
) [inline]

Starts an InParallel chain with the given processes.

*ITERATOR is expected to resolve to a ThreadCSProcessPtr

ParallelHelperOneThread csp::InParallelOneThread ( CSProcessPtr  p  )  [inline]

Starts an InParallelOneThread chain with the given process.

See the top of this page for information on how to use this function.

template<typename ITERATOR>
ParallelHelperOneThread csp::InParallelOneThread ( ITERATOR  begin,
ITERATOR  end 
) [inline]

Starts an InParallelOneThread chain with the given processes.

*ITERATOR is expected to resolve to a CSProcessPtr

SequentialHelper csp::InSequence ( ThreadCSProcessPtr  p  )  [inline]

Starts an InSequence chain with the given process.

See the top of this page for information on how to use this function.

template<typename ITERATOR>
SequentialHelper csp::InSequence ( ITERATOR  begin,
ITERATOR  end 
) [inline]

Starts an InSequence chain with the given processes.

*ITERATOR is expected to resolve to a ThreadCSProcessPtr

SequentialHelperOneThread csp::InSequenceOneThread ( CSProcessPtr  p  )  [inline]

Starts an InSequenceOneThread chain with the given process.

See the top of this page for information on how to use this function.

template<typename ITERATOR>
SequentialHelperOneThread csp::InSequenceOneThread ( ITERATOR  begin,
ITERATOR  end 
) [inline]

Starts an InSequenceOneThread chain with the given processes.

*ITERATOR is expected to resolve to a CSProcessPtr


Generated on Mon Aug 20 12:24:28 2007 for C++CSP2 by  doxygen 1.4.7