The following paper was originally published in the Proceedings of the Fifth Annual Tcl/ 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

return addresses and lo cal variables.

communicate. However, even with PtTcl, the same

In a compiled program, the hardware in co op era-

stil l cannot beaccessed by more than one

tion with the op erating system and thread

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" 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 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 . 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/

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 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 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 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 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.