Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Multi-Threaded use of Tcl Interpreters

327 views
Skip to first unread message

Dyauspita

unread,
Sep 21, 2011, 8:32:41 PM9/21/11
to
Hi,

I have a multi-threaded application and I would like to use multiple
Tcl interpreters (several per thread for different purposes) and have
them execute independent of one another (they mainly perform lots of
calculations using expr and return values to the calling thread.

This all works fine until someone goofs up and causes an exception.
Then the message gets printed out just fine, but later on (after all
of the threads have stopped their processing) when I try to clean up
the interpreters, the Tcl channels are corrupted. I think this is due
to the way that I am initializing and destroying the interpreters.

What I do is, rougly:

in the main thread, I create an array of pointers to a data structure
that has a bunch of thread specific information including the Tcl
interpreters that thread will use. The pointer starts out NULL.

start up the threads.

When a task is sent off to a thread the thread first checks to see if
it has its thread specific data - if not, create the data structure,
fire up the interpreters using Tcl_CreateInterp, feed in a bunch of
Tcl code and then continue on. Subsequent tasks dispatched to that
thread will use the same Tcl interpreters and other data structures by
accessing them via thread index the above mentioned array of
pointers. At various points, the thread will select one of its Tcl
interpreters, feed something to it using Tcl_EvalObjv and get the
answer back using Tcl_GetObjResult. The Tcl interpreters don't need
any knowledge of one another and don't interact with one another.
They simply get called and yield back an answer.

Once all of the tasks are done, the main thread walks through the
array of pointers and where ever it finds one that was used, it shuts
everything down and destroys the interpreters using
Tcl_DeleteInterp. What I find is that if I generate exceptions in
the Tcl interpreters by feeding them bad code, they throw exceptions
as expected. However, once I go to clean them up with
Tcl_DeleteInterp, they have a corrupted channel list and they panic.

Is there something that I am not doing correctly with respect to
creating and deleting the interpreters?

Alexandre Ferrieux

unread,
Sep 22, 2011, 2:25:34 AM9/22/11
to
On 22 sep, 02:32, Dyauspita <philb...@gmail.com> wrote:
>
> Is there something that I am not doing correctly with respect to
> creating and deleting the interpreters?

As described, the task is entirely doable at script level:

- one [thread::create] to generate the per-thread master interpreter
- [interp create] (run by the thread's master) to generate the
slaves

It is generally a good idea to aim for 100% script while possible.

-Alex

Donal K. Fellows

unread,
Sep 22, 2011, 6:09:46 AM9/22/11
to
On 22/09/2011 07:25, Alexandre Ferrieux wrote:
> - one [thread::create] to generate the per-thread master interpreter
> - [interp create] (run by the thread's master) to generate the
> slaves

Yep, and the master interpreter takes care to trap any errant errors
bubbling up out of the slaves so that they can be cleaned up properly
while leaving the overall thread still able to respond to messages. All
easily done with [catch]. Which is of course what Alexandre was thinking
of. The only tricky bit is that it is probably best to have the slave
interpreters not know about communication between threads, leaving that
firmly in the hands of the masters (which do it right). Aliases in the
slaves can make this very easy to handle for slave code.

I can't tell offhand whether making the slaves be safe interpreters is a
good idea or not. Depends on the details of the application (and is
really an independent decision anyway).

Donal.

Dyauspita

unread,
Sep 22, 2011, 10:59:14 AM9/22/11
to

100% script isn't possible - this is a very large performance
sensitive C/C++ application and wouldn't do at all well being ported
to Tcl. If I were to try creating all of the interpreters in Tcl, how
would I best communicate between my C++ task threads and my Tcl
interpreter threads? Is there an interface from which I can get the
Tcl_Interp* 's for each of the created interpreter threads? Will I go
through a context switch in getting from the C++ thread to the Tcl
thread? I am worried about any extra overhead required for passing
these calculations all through the main interpreter if it has to be
done that way. The calculations themselves are short and execute
quickly, but there are millions of them going on - hence the desire to
put them in parallel.

One other point that seems pertinent is that in the case that is
crashing, each of the interpreters is opening a file (the same file)
and writing to it.

On Sep 22, 3:09 am, "Donal K. Fellows"

Don Porter

unread,
Sep 22, 2011, 11:23:24 AM9/22/11
to
Dyauspita wrote:
> Is there something that I am not doing correctly with respect to
> creating and deleting the interpreters?

Can't say for certain if this is the issue, but do you
Tcl_Preserve() each interp before you start to use it and Tcl_Release()
it afterwards? If not, does adding these calls make the problem go
away? Is this a problem you can reliably reproduce?

--
| Don Porter Applied and Computational Mathematics Division |
| donald...@nist.gov Information Technology Laboratory |
| http://math.nist.gov/~DPorter/ NIST |
|______________________________________________________________________|

Dyauspita

unread,
Sep 22, 2011, 2:09:37 PM9/22/11
to
On Sep 22, 8:23 am, Don Porter <d...@nist.gov> wrote:

> Can't say for certain if this is the issue, but do you
> Tcl_Preserve() each interp before you start to use it and Tcl_Release()
> it afterwards?  If not, does adding these calls make the problem go
> away?  Is this a problem you can reliably reproduce?

I just looked at Tcl_Preserve and it doesn't look like it works on
interps, but on clientdata. Is there another usage scenario that I
didn't see?

I don't use either of those - my application has very little
interaction with the interpreter - just call the interpreter passing
in a call to an already loaded proc and then get the answer back.

I have been playing around with reproducing the issue - it is very
simple inside my application - it doesn't require an actual exception,
only that I use open and then don't close the file that I opened. So
my proc now looks like:

This causes a crash:
proc foo { arg } {
set log [ open logfile a ]
return 3.4
}

This doesn't:
proc foo { arg } {
set log [ open logfile a ]
close $log
return 3.4
}

I haven't tries putting this into a separate stand along threaded C
application, but I'll try that next.

Alexandre Ferrieux

unread,
Sep 22, 2011, 2:57:45 PM9/22/11
to
On Sep 22, 4:59 pm, Dyauspita <philb...@gmail.com> wrote:
> 100% script isn't possible - this is a very large performance
> sensitive C/C++ application and wouldn't do at all well being ported
> to Tcl.

OK. But what about using each part where it excels ?
- in Tcl, all the high-level glue: threads, masters, slaves, task
initialization, data loading.
- in C, the number crunching, exposed to Tcl as an extension
registering new commands.

This way you'll avoid messing with the Tcl-C API except for a very
narrow part (your new commands' arguments and results). You'll be glad
you avoided all the details of interp initialization stuff, and also
all thread reentrancy issues, TSD access, etc.

-Alex

Dyauspita

unread,
Sep 22, 2011, 4:00:17 PM9/22/11
to
On Sep 22, 11:57 am, Alexandre Ferrieux <alexandre.ferri...@gmail.com>
wrote:

> OK. But what about using each part where it excels ?

I am using Tcl as a user extension for user defined calculations - so
Tcl excels there because it is interpreted and I don't have to ship a
build environment with my application so that customers can link in
their C/C++ code to do the calculations.

Usually the customer supplies a proc that loops over a bunch of values
that it can retrieve from an application object passed in and return a
double that the application stashes away for use later on.

This works and performs well. The problem is that if the user colors
outside the lines in an interesting way, like opening a file, the
application crashes.

I have reproduced this in a small testcase contained entirely in C++.

I also found that by deleting the interpreter inside the same
execution thread that it was created in, I can also run successfully
(see commented code). This is difficult for me to do as I don't know
when the last task is done on each thread inside the thread context -
only the main thread knows when everything is done.


#include <pthread.h>
#include <iostream>
#include <vector>
#include <tcl.h>

using namespace std;

vector<Tcl_Interp*> interpreters;

const char* tcl_proc = "proc foo { } {\n"
" set log [ open logfile a ]\n"
" puts $log \"thread started\"\n"
// " close $log\n"
" return 3.4\n"
"}\n"
"foo\n"
"\n";


void* create_a_task_handler( void* slot_p ) {

unsigned long slot_index = *((unsigned long*)slot_p);
Tcl_Interp *interp = interpreters[slot_index] =
Tcl_CreateInterp();
cout << "Executing " << slot_index << endl;
int rc = Tcl_Eval( interp, tcl_proc );
if ( rc != TCL_OK ) {
cout << "An error was encountered during evaluation of TCL
code.";
const char * errorInfoStr = Tcl_GetVar(interp, "errorInfo",
TCL_GLOBAL_ONLY);
if ( errorInfoStr ) cout << errorInfoStr << endl;
}
Tcl_Obj *res = Tcl_GetObjResult( interp );
double ret_val;
rc = Tcl_GetDoubleFromObj(interp, res, &ret_val );
cout << "Proc returned " << ret_val << endl;

// Everything is fine if we delete the interpreters in the same
thread they were created in.
//Tcl_DeleteInterp( interpreters[slot_index] );
//interpreters[slot_index] = NULL;

return NULL;
}

main( int argc, const char** argv ) {

const char* usage = "Usage: tcl_mt [<thread_count> ]\n
thread_count is {1 2 4 8 or 16}\n\n";

const int MAX_THREADS=16;
int thread_count=1;
if ( argc>1 ) {
thread_count=atoi(argv[1]);
}
if ( thread_count != 1 && thread_count != 2 && thread_count != 4
&& thread_count != 8 && thread_count != 16 )
{
cout << "thread count must be 2,4,8 or 16." << endl;
cout << usage << endl;
exit(1);
}


pthread_t threads[MAX_THREADS];
unsigned long slots[MAX_THREADS];

interpreters.assign(thread_count, NULL);

if ( argc == 1 ) {
cout << "Running tasks in main thread." << endl;
// straight ahead - no threads.
slots[0] = 0;
create_a_task_handler( &slots[0] );
Tcl_DeleteInterp( interpreters[0] );
} else {

cout << "Running " << thread_count << " threads." << endl;

for( unsigned long i=0; i != thread_count; ++i ) {
slots[i] = i;
pthread_create( &(threads[i]), NULL,
create_a_task_handler, &slots[i] );
}
for( unsigned long i=0; i != thread_count; ++i ) {
pthread_join( threads[i], NULL );
}
for( unsigned long i=0; i != thread_count; ++i ) {
if ( interpreters[i] ) {
Tcl_DeleteInterp( interpreters[i] );
}
}
}
}


For single threaded execution:

tcl_mt
Running tasks in main thread.
Executing 0
Proc returned 3.4

this works fine,

now for multi-threaded execution:

tcl_mt 2

Running 2 threads.
Executing 0
Executing 1
Proc returned 3.4
Proc returned 3.4
FlushChannel: damaged channel list
Aborted


Gerald W. Lester

unread,
Sep 22, 2011, 4:11:42 PM9/22/11
to
On 9/22/11 3:00 PM, Dyauspita wrote:
> On Sep 22, 11:57 am, Alexandre Ferrieux<alexandre.ferri...@gmail.com>
> wrote:
>
>> OK. But what about using each part where it excels ?
>
> ... The problem is that if the user colors
> outside the lines in an interesting way, like opening a file, the
> application crashes.
>
> I have reproduced this in a small testcase contained entirely in C++.

If you are saying you can produce the problem without the use of Tcl, then
it sounds like you may have a non-thread safe C/C++ library being used.

> I also found that by deleting the interpreter inside the same
> execution thread that it was created in,

Where else would you be deleting it from -- an interpreter is not supposed
to be accessed by more than one thread. You may have may interpreters per
thread (i.e. a thread can access/use many interpreters), but only one thread
per interpreter (i.e. only a single thread should be accessing a given
interpreter).

> ...


--
+------------------------------------------------------------------------+
| Gerald W. Lester, President, KNG Consulting LLC |
| Email: Gerald...@kng-consulting.net |
+------------------------------------------------------------------------+

Dyauspita

unread,
Sep 22, 2011, 4:48:59 PM9/22/11
to
On Sep 22, 1:11 pm, "Gerald W. Lester" <Gerald.Les...@KnG-
Consulting.net> wrote:
> On 9/22/11 3:00 PM, Dyauspita wrote:

> > I have reproduced this in a small testcase contained entirely in C++.
>
> If you are saying you can produce the problem without the use of Tcl, then
> it sounds like you may have a non-thread safe C/C++ library being used.
OK, poor wording on my part - I have a small testcase contained
entirely in C++ and Tcl API calls.

>
> > I also found that by deleting the interpreter inside the same
> > execution thread that it was created in,
>
> Where else would you be deleting it from -- an interpreter is not supposed
> to be accessed by more than one thread.  You may have may interpreters per
> thread (i.e. a thread can access/use many interpreters), but only one thread
> per interpreter (i.e. only a single thread should be accessing a given
> interpreter).

That was the original question:

> Once all of the tasks are done, the main thread walks through the
> array of pointers and where ever it finds one that was used, it shuts
> everything down and destroys the interpreters using
> Tcl_DeleteInterp. ...
> Is there something that I am not doing correctly with respect to
> creating and deleting the interpreters?

So the answer is "Yes, you have to create, execute, and delete the
interpreters all on the same thread". Right?

Alexandre Ferrieux

unread,
Sep 22, 2011, 7:03:25 PM9/22/11
to
On Sep 22, 10:00 pm, Dyauspita <philb...@gmail.com> wrote:
> On Sep 22, 11:57 am, Alexandre Ferrieux <alexandre.ferri...@gmail.com>
> wrote:
>
> > OK. But what about using each part where it excels ?
>
> I am using Tcl as a user extension for user defined calculations - so
> Tcl excels there because it is interpreted and I don't have to ship a
> build environment with my application so that customers can link in
> their C/C++ code to do the calculations.

You missed the point. Clearly you're encountering problems at the C
level, and I suspect that by reducing your C part to the CPU-intensive
part and *not* the interp+thread glue, you'll get the same speed and
none of your bugs.

-Alex

David Gravereaux

unread,
Sep 22, 2011, 8:01:41 PM9/22/11
to
Maybe having a look at the thread extension source might help. Sounds
like a resource isn't being properly disposed of. Maybe a missing call
to Tcl_FinalizeThread() before the thread that had called
Tcl_CreateInterp() was closed.
--


signature.asc

Dyauspita

unread,
Sep 22, 2011, 8:44:23 PM9/22/11
to
> You missed the point. Clearly you're encountering problems at the C
> level, and I suspect that by reducing your C part to the CPU-intensive
> part and *not* the interp+thread glue, you'll get the same speed and
> none of your bugs.

No, my problem was misuse of the Tcl threading architecture in that I
was
not following the strict linkage of strictly linking creation, use and
destruction of a single interpreter to a single thread. Luckily I
won't
need to take on the gargantuan re-factoring that you suggest above.

David Gravereaux

unread,
Sep 23, 2011, 12:16:11 AM9/23/11
to
On 09/22/2011 05:44 PM, Dyauspita wrote:
> strictly linking creation, use and
> destruction of a single interpreter to a single thread.

Yup, go outside that rule and explosions are expected.
--


signature.asc

Gerald W. Lester

unread,
Sep 23, 2011, 12:48:11 AM9/23/11
to
On 9/22/11 3:48 PM, Dyauspita wrote:
> On Sep 22, 1:11 pm, "Gerald W. Lester"<Gerald.Les...@KnG-
> Consulting.net> wrote:
>> On 9/22/11 3:00 PM, Dyauspita wrote:
>...

> So the answer is "Yes, you have to create, execute, and delete the
> interpreters all on the same thread". Right?

That is my understanding.

Bezoar

unread,
Sep 24, 2011, 11:28:40 AM9/24/11
to
On Sep 22, 11:48 pm, "Gerald W. Lester" <Gerald.Les...@KnG-
Consulting.net> wrote:
> On 9/22/11 3:48 PM, Dyauspita wrote:
>
> > On Sep 22, 1:11 pm, "Gerald W. Lester"<Gerald.Les...@KnG-
> > Consulting.net>  wrote:
> >> On 9/22/11 3:00 PM, Dyauspita wrote:
> >...
> > So the answer is "Yes, you have to create, execute, and delhete the
> > interpreters all on the same thread".   Right?
>
> That is my understanding.
>
> --
> +------------------------------------------------------------------------+

Have you tried to use a tcl library that has not been compiled
withoutsuport for threading?You seem to be doing all thread operations
using pthread calls exclusive of tcl. Since they dont interact with
one another this may work. Its an experiment of course the functions
that tcl calls would still have to be re-entrant or strange things may
happen. Alsobe sure that you compile your own tcl so thread libraries
are the same. Another approach may be to create athread pool that
simply sends the jobs to an independent process that evaluates the
script and returns the result. Using a pool eliminates the start up
cost of the thread and you can communicate vis pipes locally or
sockets to utilize othercomputers

Donal K. Fellows

unread,
Sep 29, 2011, 6:54:17 AM9/29/11
to
On 22/09/2011 19:09, Dyauspita wrote:
> I just looked at Tcl_Preserve and it doesn't look like it works on
> interps, but on clientdata. Is there another usage scenario that I
> didn't see?

The ClientData type is just a void* under the covers; the contract on
Tcl's handling of values of declared type ClientData is that Tcl won't
mess with it at all other than in exactly the ways that *you* specify.
In particular, with Tcl_Preserve/Tcl_Release there will be no peeking
inside (except for if you use Tcl_EventuallyFree with the option
TCL_DYNAMIC, in which case it will be ckfree()d once it's no longer
preserved by anything).

In short, ClientData means this is your data, not Tcl's.

Donal.

Joe English

unread,
Sep 29, 2011, 3:03:02 PM9/29/11
to
On 2011-09-22, Dyauspita wrote:
> [...]
> What I do is, roughly:
> [...]
> When a task is sent off to a thread the thread first checks to see if
> it has its thread specific data - if not, create the data structure,
> fire up the interpreters using Tcl_CreateInterp, feed in a bunch of
> Tcl code and then continue on. Subsequent tasks dispatched to that
> thread will use the same Tcl interpreters and other data structures by
> accessing them via thread index the above mentioned array of
> pointers. At various points, the thread will select one of its Tcl
> interpreters, feed something to it using Tcl_EvalObjv and get the
> answer back using Tcl_GetObjResult. The Tcl interpreters don't need
> any knowledge of one another and don't interact with one another.
> They simply get called and yield back an answer.

That sounds OK, until you get to this part;

> Once all of the tasks are done, the main thread walks through the
> array of pointers and where ever it finds one that was used, it shuts
> everything down and destroys the interpreters using
> Tcl_DeleteInterp.

If you create a Tcl_Interp * in one thread, then all
operations on that interp -- including deleting it! --
must be done in that same thread.

Try moving the cleanup code from the main thread
into the slave thread, see if that fixes things.


--Joe English
0 new messages