This example also shows how to include a statically linked extension into our proggie, with automatic bootstrap of the extension from within C space - so that Perl space user scripts don't have to explicitly `use' the extension, which they'd always have to otherwise, as long as they'd be intended to run in our custom embedded interpreter.
The C++ program with embedded Perl is statically linked with
the custom extension Emb_lib, with the Thread module and also with
the DynaLoader module, to provide for autoloading of any other
modules.
The following measures need to be taken in order to properly
link-in and bootstrap the three aforementioned modules:
This example also uses a "private" build of Perl, rather than
the system-wide build. Said "private" build resides in a dedicated
subdirectory of the current directory of this README.
I have developed this scenario when I needed to build a custom
Perl with support for threads (5.005-style posixish stuff).
More precisely, to save space, you have to provide the private Perl build yourselfs. The Makefile in this directory expects a subdirectory called
perl5.005_03
with a built installation of perl
tar xvzf perl5.005_03.tgz cd perl5.005_03 ./Configure -Dusethreads -des make
(-d = use defaults, -e = proceed past the production of config.sh, -s = silent) This way, configure will use defaults and will not bother you with progress messages.
Note that patch_xs_makemaker now does some heavy modifications to the resulting $(Ext_Dir)/Makefile to force make to use the custom Perl build rather than the system-wide install.
The emb_main.cc in this example contains a lot of code cannibalized out of Thread.c. For easy comparison with Thread.c, there are a myriad of lines containing the original code, commented out with `//-' which means that this stuff can safely be cut out. Large sections of code in irrelevant #ifdefs have been cut out completely.
The relevant contents of Thread.c can be described in the following way:
static THREAD_RET_TYPE threadstart(void *arg) { // Running in the child thread - the thread body itself. // This is what Perl passes to pthread_create() as the thread code function. does some initialization (perlembed-style macros) - namely, POPs a reference to the Perl-space sub to run as perl-space thread body calls the sub indicated to `new Thread' as the thing to run (using perl_call_sv()) so that the Sub just run finds the array of arguments as the topmost item on the perl stack and POPs it does some cleanup (perlembed-style macros) - pops results off the stack and transforms them into a new AV*, ready to be returned upon raw pthread_join() to whoever waits for the thread to join. // Essentially this seems to be a wrapper between raw pthread_create() and // the perl-space sub, that should run as a separate thread. // This is analogous to the C style of Posix Threads - only some wrapping is // apparently necessary to make it work `seamlessly' the same way in Perl. // // When control is returned by the Perl-space sub to this function, the thread // is about to end. Hopefully someone is waiting for it to join(), so that it // doesn't become a zombie. } static SV *newthread (SV *startsv, AV *initargs, char *classname) { // Running in the parent thread - the thread that acts to splint off a child. does some initialization - creates some crude C structs to hold per-thread bootstrap data, XPUSHes on stack a reference to the array of arguments and XPUSHes on stack the reference to the Perl-space sub to run as Perl-space thread body. calls pthread_create() and passes threadstart() as an argument. does a basic error check on the return value of pthread_create and finishes some stuff on behalf of the child thread. // Obviously when this function finishes, the thread just splinted keeps // running on - thus, this function is not the one to collect the return // values of the Perl-space sub comprising the thread body. To collect data // from a finished child thread, the parent thread has to wait() for the // child to join(). } // Please note how the parent thread locks the thread-specific bootstrap // struct (using MUTEX_LOCK on a mutex within the struct), then sets the // child airborne using pthread_create(), and if no problem is signaled, // it performs a few final polishing strokes on the child's bootstrap // struct - while the child pauses for a while, waiting to lock the mutex, // until the master thread is done with its creator business and releases // the lock (using MUTEX_UNLOCK). After that, the child goes on with its // threadstart() business, rushing to finally launch the Perl-space sub - // while newthread() returns within the parent thread, purring with joy // looking back at the good job it has done. // // If you try to catch trace of the typical perlembed sequence of macros, // you'll find out that indeed there is such a sequence (the "sub to run" // parameter being somewhat of a deviation) - it's just that the input // parameters are XPUSH'ed within the master thread, the sub is run // within the child thread and the return data is collected in the child // thread as well - or, more precisely, the array of return values // can be returned by join() called within the master thread... static void remove_thread(struct perl_thread *t) { destroys the thread-specific bootstrap struct. // Detached threads call this themselves just before they die, // from within threadstart() - after the called Perl-space sub // returns control. // With joinable threads, this cleanup is done by the thread that // wait()s for the children to join(). }
If you want to run multi-threaded Perl in threads that you create yourself, all you need to do is dissect the standard Thread::new().
There are two key functions that take care of that - one runs
in the parent thread, the other in the child thread.
The parent function calls pthread_create() in its middle,
the child function calls the Perl sub passed as the Perl
thread function - again, just about in the middle of its length.
All we need to do is split the parent and child lead-in and lead-out into separate functions (or perhaps macros) that can be called easily from your C++ code - while you handle pthread_create() and the Perl sub calls yourselfs.
Note that if you dissect the lead-in and lead-out into separate functions, you need to pass a couple of variables from the lead-in to the respective lead-out. Not a big problem though.
If you include/bootstrap Thread.pm, you can use the out-of-the-box Thread::methods and stuff within such in-vitro child threads :)
I haven't tested the whole thing extensively - it has only
started to behave somewhat properly in this state. The example
shows a basic concurrency and reentrancy test.
Perhaps I should add some more defensive error checking in the
lead-outs, so that there are no zombies and the children die
immediately when the parent dies.
Also, some stress tests for multithreaded operation over
a shared interpreter instance could be useful. If some operations
prove troublesome with concurrent access, additional explicit locking
(serialization) could be employed.