The following paper was originally published in the Proceedings of the Fifth Annual Tcl/Tk Workshop Boston, Massachusetts, July 1997
PtTcl: Using Tcl with Pthreads
D. Richard Hipp Hwaci, Charlotte, NC Mike Cruse CTI, Ltd., Prescott, AZ
For more information about USENIX Association contact: 1. Phone: 510 528-8649 2. FAX: 510 548-5738 3. Email: [email protected] 4. WWW URL: http://www.usenix.org
PtTcl: Using Tcl with Pthreads
D. Richard Hipp Mike Cruse
Hwaci CTI, Ltd.
6200 Maple Cove Lane 1040 Whipple St. 225
Charlotte, NC 28269 Prescott, AZ 86301
Abstract This article describ es a new implementation of
multi-threaded Tcl that is based on POSIX threads
[Pthreads] and works with Tcl version 7.6. An
upgrade to Tcl version 8.0 is planned. We call
Tcl is not thread-safe. If two or more threads at-
the package \PtTcl". PtTcl b orrows some of
tempt to use Tcl at the same time, internal data
Jankowski's ideas but is a completely new imple-
structures can be corrupted and the program can
mentation.
crash. This is true even if the threads are using
separate Tcl interpreters.
PtTcl is a modi cation to the Tcl core that makes
Tcl safe to use with POSIX threads. With PtTcl,
2 Threading Mo del
each thread can create and use its own Tcl inter-
preters that wil l not interfere with interpreters used
The usual mo del for a multi-threaded program is
in other threads. A message-passing mechanism al-
that each thread has its own stack used to store
lows Tcl interpreters running in di erent treads to
subroutine return addresses and lo cal variables.
communicate. However, even with PtTcl, the same
In a compiled program, the hardware in co op era-
interpreter stil l cannot beaccessed by more than one
tion with the op erating system and thread library
thread.
take care of providing and managing these separate
stacks. But in an interpreted language like Tcl,
This paper describes the design, implementation and
the interpreter must create and manage the sepa-
use of PtTcl.
rate stacks itself. The standard Tcl interpreter only
makes provisions for a single stack. In order to make
Tcl truly multi-threaded, it is necessary to change
the Tcl core to allowmultiple stacks p er interpreter.
1 Intro duction
Unfortunately, the concept of one stack per inter-
preter is a fundamental assumption in the design of
Tcl was originally designed for use in single-
Tcl, and to change this assumption would require
threaded programs only. But recently, there has
a ma jor rewrite of manykey routines. In order to
b een an increasing need to use the p ower of Tcl in
avoid excessive rework of the Tcl core, wechose to
applications that contain multiple threads of con-
use a more restrictive thread mo del for PtTcl.
trol. Unfortunately, the core Tcl library uses several
static data structures that can b ecome corrupted if PtTcl allows an application to havemultiple Tcl in-
accessed simultaneously bytwo or more threads. A terpreters running in indep endent threads, and each
program crash is the usual result. thread in a PtTcl program can contain anynumber
of interpreters including zero. But PtTcl only al-
There is a least one prior e ort to make Tcl thread-
lows an interpreter to b e run from a single thread. If
safe. Steve Jankowski created a mo di ed version of
another thread tries to use an interpreter, an error
the Tcl sources called MTtcl [MtTcl] which allows
message is returned.
the use of Tcl in a multi-threaded environment. But
Jankowski's implementation only works with Solaris In ordinary Tcl, there is a single event queue used
threads and on Tcl versions 7.4 and earlier. to pro cess all timer and le events. In PtTcl, this
used by PtTcl: concept is extended to one event queue p er thread.
The fact that each thread has its own event queue is
a necessary consequence of the restriction that Tcl
A single thread can contain anynumb er of Tcl
interpreters must always b e run in the same thread.
interpreters.
Recall that the usual action taken when an event ar-
rives is for a Tcl script to run in resp onse. Supp ose
A particular Tcl interpreter may only b e used
an interpreter in thread A registers to receive an
from within a single thread.
event, but the event arrives while executing thread
Each thread has its own event queue.
B. There is no way for the receiving interpreter, run-
ning in thread B, to execute the desired script b e-
A message in the form of a Tcl script can b e
cause that script can only be run from thread A.
sent to the main Tcl interpreter in any thread.
Hence, if we want to be able to invoke scripts in
resp onse to events, each thread must have its own
Tcl variables may be shared between two or
event queue.
more Tcl interpreters, even interpreters run-
ning in separate threads.
Each thread has the concept of a main interpreter.
The main interpreter is di erent from other inter-
preters in the same thread in only one way: you can
send messages to the main interpreter.
3 New Tcl Commands
Messages are a new kind of Tcl event, so a Tcl in-
terpreter running in a given thread will not pro cess
The PtTcl package implements two new Tcl com-
any messages until it visits its event lo op. A Tcl in-
mands. The \shared" command is used to desig-
terpreter visits its event lo op whenever it executes
nate variables that are to b e shared with other in-
one of the standard Tcl commands vwait or update
terpreters, and the \thread" command is used to
or one of two commands added by PtTcl: thread
create and control threads.
eventloop and thread send.
Messages can b e either synchronous meaning they
3.1 The \shared" command
will wait for a resp onse or asynchronous re and
forget. The result returned to the sending thread
from a synchronous message is the result of the Tcl
The shared is similar to the standard global com-
script in the receiving thread or an error message if
mand. Shared takes one or more arguments which
the message could not b e sent for some reason. An
are names of variables that are to be shared by
asynchronous message has no result usually, but it
all Tcl interpreters, including interpreters in other
still might return an error if the message could not
threads. Note that b oth interpreters must execute
be sent. Asynchronous messages can b e broadcast
the shared command indep endently b efore they will
to all main interpreters or to all main interpreters
really b e using the same variable.
except the interpreter that is doing the sending.
Unfortunately, the trace command will not work on
A message can b e sent from anyinterpreter, not just
shared variables. This is another consequence of the
the main interpreter, or directly from C co de. There
fact that an interpreter can only b e used in a single
do es not have to b e a Tcl interpreter running in a
thread. When a trace is set on a variable, a Tcl
thread in order for that thread to send a message,
script is run whenever that variable is read, written
but a main interpreter is necessary for the message
or deleted. But, if the trace was set by thread A
to b e received.
and the variable is changed by thread B, there is no
way for thread B to invoke the trace script in thread
Most variables used by a Tcl interpreter are private
A.
to that interpreter. But PtTcl implements a mech-
anism for sharing selected variables b etween twoor
more interpreters, even interpreters running in dif-
3.2 The \thread" command
ferent threads. However, it is not p ossible to put
trace events on shared variables, which limits their
The thread command is more complex than
usefulness.
shared. Thread contains nine separate sub com-
Here is a quick summary of the execution mo del
mands used to create new threads, send and receive
thread broadcast message messages, query the thread database, and so forth.
[-sendtoself boolean] Each is describ ed separately b elow.
The thread broadcast works like thread send ex-
thread self
cept that it sends the message to all threads and it
always op erates asynchronously. Normally, it will
Every thread in PtTcl that contains an interpreter is
not send the message to itself, unless you also sp ec-
assigned a unique p ositiveinteger Id. This Id is used
ify the -sendtoself ag.
by other thread commands to designate a message
recipient or the target of a join. The thread self
thread update
command returns the Id of the thread that executes
the command.
This command causes the current thread to pro cess
all p ending messages, that is, messages that other
thread create [command] [-detach
threads have sent and are waiting for this thread
boolean]
to pro cess. Only thread messages are pro cessed by
this command { other kinds of p ending events are ig-
New threads can be created using the thread
nored. If you want to pro cess all p ending events in-
create command. The optional argument to this
cluding thread messages, use the update command
command is a Tcl script that is executed by the new
from regular Tcl.
thread. After the sp eci ed script is completed, the
new thread exits. If no script is sp eci ed, the com-
thread eventloop
mand \thread eventloop" is used instead. As-
suming the new thread is created successfully, the
thread create command returns the thread Id of
This command causes the current thread to go into
the new thread.
an in nite lo op pro cessing events including incom-
ing messages. This command will not return until
After a thread nishes executing its Tcl script, it
the interpreter is destroyed by either an exit com-
normally waits for another thread to join with it
mand or a interp destroy fg command.
and takes its return value. See the thread join
command b elow. But if the -detach option evalu-
thread join [-id Id][-timeout mil lisec-
ates to true, then the thread will terminate imme-
onds]
diately up on nishing its script. A detached thread
can never b e joined.
The thread join command causes the current
Note that the joining and detaching of threads is an
thread to join with another thread that has com-
abstraction implemented by the PtTcl library. From
pleted pro cessing. The return value of this com-
the p oint of view of the Pthreads library, all threads
mand is the result of the last command executed
created by the thread create command run de-
by the thread that was joined. By default, the rst
tached.
available thread is joined. But you can wait on a
particular thread by using the -id option.
thread send whom message [-async
The calling thread will wait inde nitely for another
boolean]
thread to join unless you sp ecify a timeout value.
When a timeout is sp eci ed, the thread join will
Use the thread send command to send a message
return after that timeout regardless of whether or
from one thread to another. The arguments to this
not it has found another thread to join. A timeout
command sp ecify the target thread and the message
of zero 0 can b e used if you want to quickly see if
to b e sent. The message is simply a Tcl script that is
any threads are waiting to b e joined.
executed on the remote thread. The thread send
command normally waits for the message to com-
thread list
plete on the remote thread, then returns the result
of the script. But, if the -async option is true, the
This command returns a list of Tcl thread Id num- thread send will return immediately, not waiting
b ers for each existing thread. on a reply.
thread yield int Tcl_ThreadSend
int toWhom,
char **replyPtr,
Finally, the thread yield command causes the cur-
char *format,
rent thread to yield its timeslice to some other
...
thread that is ready to run, if any.
;
4 New C Functions
The Tcl ThreadSend function allows C or C++
co de to send a message to the main Tcl interpreter
in another thread. The rst argument is the Tcl
In addition to the new Tcl commands, PtTcl also
thread Id number not the pthread t identi er of
provides several new C functions that can b e used
the destination thread. You can sp ecify a destina-
by C or C++ programs to create and control Tcl
tion of zero 0 in order to broadcast a message.
interpreters in a multi-threaded environment.
The second parameter is used for the reply. The
message resp onse is written into memory obtained
int Tcl_ThreadCreate
from ckalloc and **replyPtr is made to p oint
char *cmdText,
to this memory. If the value of the second parameter
void *initProcTcl_Interp*,void*,
is NULL, then the message is sent asynchronously. If
void *argPtr
the rst parameter is 0, then the second parameter
;
must b e NULL or else an error will b e returned and
no messages will b e sent.
The Tcl ThreadCreate function creates a new
The third parameter is a format string in the style
thread and starts a Tcl interpreter running in that
of printf that sp eci es the message that is to b e
thread. The rst argument is the text of a Tcl script
sent. Subsequent arguments are added as needed,
that the Tcl interpreter running in the new thread
exactly as with printf.
will execute. You can sp ecify NULL for this rst argu-
ment and the Tcl interpreter will execute the com-
The return value from Tcl ThreadSend is the re-
mand thread eventloop. The second argumentto
turn value of the call to Tcl Eval in the des-
Tcl ThreadCreate is a p ointer to a function that
tination thread, if this is a synchronous message.
can b e used to initialize the new Tcl interpreter b e-
For an asynchronous message, the return value is
fore it tries to execute its script. The third argu-
TCL OK unless an error prevents the message from
ment is the second parameter to this initialization
b eing sent.
function. Either or b oth of these arguments can b e
NULL. All threads created by Tcl ThreadCreate
are detached.
Tcl_Interp *Tcl_GetThreadInterp
The Tcl ThreadCreate returns an integer which
Tcl_Interp *interp
is the Tcl thread Id of the new thread it creates.
;
This is exactly the same integer that would have
b een returned if the thread had b een created using
the thread create Tcl command.
The Tcl GetThreadInterp routine will return a
The Tcl ThreadCreate may be called from a
p ointer to the main interpreter for the calling
thread that do es not itself have a Tcl interpreter.
thread. If the calling thread do es not have a main
This function allows threads that do not use Tcl to
interpreter, then the interpreter sp eci ed as its ar-
create subthreads that do.
gument is made the main interpreter. If the argu-
ment is NULL, then Tcl Note that the *initProc function might not CreateInterp is called
have executed in the new thread by the time to create a new Tcl interpreter which b ecomes the
Tcl ThreadCreate returns, so the calling func- main interpreter. At the conclusion of this function,
tion should not delete the argPtr rightaway. It is the calling thread is guaranteed to have a main in-
safer to let the *initProc take resp onsibility terpreter and a p ointer to that interpreter will be
for cleaning up argPtr. returned.
in the implementation of Tcl 5 Changes Made To The Tcl Core ThreadSend, so
even though MIT Pthreads has now b een xed the
new printf implementation must remain.
The biggest change in PtTcl is the implementation
of separate event queues for each thread. In the
New co de was added to implement the shared
standard Tcl distribution, the event queue is con-
and thread commands. The co de for shared was
structed as a linked list of structures with a static
added to \generic/tclVar.c" since it needed ac-
p ointer to the head of the list. In PtTcl, we sim-
cess to information lo cal to that le. The thread
ply converted this static p ointer into thread-sp eci c
command and the new Tcl ThreadCreate and
data. Actually, once you get into the details, you
Tcl ThreadSend functions are all found in a new
nd that more than this one static p ointer can cause
source le named \generic/tclThread.c".
problems. Dozens of static variables in Tcl had to b e
And, of course, an o ccasional mutex had to b e added
converted into thread-sp eci c data variables. And,
here and there, and some functions of the stan-
of course, mutexes had to b e added to the few static
dard C library were changed to their thread-safe
variables that were not converted to thread-sp eci c
equivalents. Example: gmtime was changed to
data.
gmtime r. Overall, the implementation of PtTcl
PtTcl changes the semantics of the exit command
was reasonably simple thanks to the extraordinarily
slightly. In ordinary Tcl, exit terminates the whole
clean implementation of the original Tcl core.
application, and so it do es not worry to o much
ab out releasing le descriptors or freeing memory
obtained from ckalloc. In PtTcl, exit will only
6 Obtaining And Building PtTcl
terminate the current thread. This necessitated
some additional clean-up actions in the Tcl core
in order to avoid le-descriptor and memory leaks.
The latest sources to PtTcl can b e found at
Corresp onding changes were made to the co de that
implements the interp command in order to get the
http://users.vnet.net/drh/pttcl.tar.gz
command
To build PtTcl, rst obtain and unpack the source
interp delete {}
tree, then cd into the directory pttcl7.6a1/unix
and enter one of the commands
to do the right thing.
./configure --enable-pthreads
The lsort command was rewritten to use a merge-
sort algorithm [Knuth] instead of the qsort func-
or
tion. This change had the side-e ect of making the
lsort command b oth recursive and stable. It turns
./configure --enable-mit-pthreads
out that it is also a little faster. This change has
b een folded into the Tcl core as of version 8.0.
Use the rst form at installations where POSIX
threads programs can b e built by linking in the sp e-
In standard Tcl, the env arrayvariable contains the
cial -lpthreads library. The second form is for in-
values of all environmentvariables. Changes made
stallations that use MIT Pthreads [Provenzano] and
to env are applied to all interpreters. This b ehavior
require the sp ecial pgcc C compiler.
is not implemented in PtTcl, however. Eachinter-
preter in PtTcl still has the env array containing
After con guring the distribution, typ e
the environment, but changes to this array are not
copied into other interpreters.
make
During early testing, we discovered that the
printf function supplied with MIT Pthreads was to build a tclsh executable. Note that if you omit
not thread-safe. Rather than x MIT Pthreads, we the
found it easier to supply our own thread-safe version --enable-pthreads or --enable-mit-pthreads
of the printf function, which we placed in the option from the ./configure command, then the
source le \generic/tclPrintf.c". We later used tclsh you build will not contain supp ort for
some enhanced features of this alternative printf Pthreads.
7 Status Of PtTcl Development 9 Availability
We develop ed and tested PtTcl under the Linux ver-
An online manual and complete source co de for
sion 2.0. For the Pthreads library, we have used
PtTcl is available from
b oth Chris Provenzano's user-level implementation
[Provenzano] also known as MIT Pthreads and
http://users.vnet.net/drh/pttcl.html
a kernel-level Pthreads implementation by Xavier
Leroy [Xavier] built on the clone system call
of Linux. Neither of these Pthreads implementa-
tions is without aw. Under some versions of MIT
References
Pthreads, the exec Tcl command did not work reli-
ably. Later versions of MIT Pthreads work b etter.
[MtTcl] \MT-Tcl" To app ear in Tcl/Tk Tools
The exec command works ne using Linux kernel
by Mark Harrison. O'Reilly & Asso ciates, Se-
Pthreads, but under heavy load, the kernel's pro cess
bastop ol, CA. Estimated publication date: June
table has b een known to b ecome corrupt, resulting
1997.
in a system crash. We susp ect that b oth of these
problems are bugs in the underlying Pthreads im-
[Pthreads] Portable Operating System Interface
plementation or the Linux kernel, not in PtTcl.
POSIX { ANSI/IEEE Std 1003.1. Institute of
Electrical and Electronics Engineers, New York,
PtTcl was written for and has b een heavily used
NY. 1996.
in a multi-pro cessor industrial controller that im-
plements its control algorithms using a data- ow
[Knuth] Algorithm L on page 165 of The Art Of
mo del. Each no de of the data- ow graph is a PtTcl
Computer Programming Vol. 3. By Donald E.
script running in its own thread. PtTcl has sur-
Knuth. Addison Wesley Publishing Company,
vived extensive abuse testing of the controller soft-
Reading, MA. 1973.
ware with no errors or memory leaks. But these
tests have only exercised those parts of PtTcl which
[Provenzano] Pthreads: General Information. A
are actually used in the controller application. The
web page by Christopher Angelo Provenzano.
shared or exec commands have not be heavily
http://www.mit.edu:8001/people/proven/p-
tested nor have there b een many attempts to run
threads.html
more than one interpreter in a thread at a time.
[Xavier] The Linux Threads Library. Aweb page by
We susp ect that bugs remain in these areas.
Xavier Leroy.
While PtTcl has so far only b een tested under Unix,
http://pauillac.inria.fr/ xleroy/linux-
there is nothing in the implementation of PtTcl that
threads/
would preclude its use under Windows or MacIn-
tosh. All that is needed is a library for the target
platform that implements basic Pthreads function-
ality. We are not aware of any such library but
susp ect that they do exist. It should not b e much
trouble to implement Pthreads as a wrapp er around
the native Windows or MacIntosh thread capability.
PtTcl only uses a few of the more basic Pthreads
routines, so most of the Pthreads library could re-
main unimplemented.
8 Acknowledgements
PtTcl was develop ed for and released by Conserva-
tion Through Innovation, Ltd., a manufacturer of
environmental and industrial control systems based in Prescott, Arizona.