mkThreads 1.2 Manual

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted. The author makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. By use of this software the user agrees to indemnify and hold harmless the author from any claims or liability for loss arising out of such use.
 

 CONTENTS

mkThreads 1.2 - Multithreading for Tcl/Tk 8.1 and higher.

Introduction
Commands
Notes
Examples
Installation
Changes
Author

 INTRODUCTION

mkThreads provides commands that allow for development of multithreaded applications under Tcl/Tk. Threads can be created that either asyncronuously execute a script or wait for scripts sent by other threads (including, of course, the main thread). Mutexes can be defined and threads can wait on so-called Conditions to synchronize with other threads. In addition, a method is implemented to define variables which are common for all threads (The thread concept of Tcl includes that each thread must have an own interpreter. Therefore, not even global variables of one thread are known to any other thread).

mkThreads 1.2 incorporates the new thread API that comes with Tcl 8.3.1, but still compiles with the old-style API for Tcl 8.1 through Tcl 8.3.0. It is stubs enabled for versions 8.3.1 and higher.

It is recommended to read all thread relevant information provided at Scriptics' web site. See also the "threads" man page that describes the concept of mutexes and conditions.

 COMMANDS

The mkThreads package adds four new commands: thread, mutex, tvar and serialize. They accept several options and arguments as follows:

thread create ?-init? ?Script?

Creates a new thread and returns the thread's identifier. If Script is specified then the new thread will execute it and then terminate. If Script is not specified then the thread will wait on the event loop to receive scripts from other threads by means of "thread eval". Script itself can also put the thread into the event loop by e.g. calling vwait at the end. If the option -init is specified, then init.tcl will be run (by means of calling Tcl_Init()).

thread eval ?-wait? ThreadId Script
thread eval ThreadId Script ?Command?

Sends a script to a thread. The thread is specified by its ID as returned from "thread create". If the option -wait is specified then the Script is executed while the sending thread is waiting on the result. The result of the Script, though executed in another thread, would then become the result of this call, including any error information in errorCode and errorInfo. If -wait is not specified, Script will be executed asynchronuously, and the call will return immediately. If, in addition, a Command is specified right after Script, then it will be executed when Script has finished. This Command will be called with at least two arguments: The first argument is a result code (0: TCL_OK, 1: TCL_ERROR, 2: TCL_RETURN), the second argument the Script's result. If Script exited with an exception, then two more arguments will be appended, errorCode and errorInfo. The sending thread must be in the event loop, e.g. by means of vwait. Command and -wait are mutual exclusive options.

thread exit

Exits the current thread, i.e. the thread that invokes this command. If this thread is the main thread, then the application is terminated just as with the exit command.

thread onexit ?Command?

Defines or returns a Tcl command, which is called when a thread exits. Command will be called with the exiting thread's ID, therefore it should be a procedure that takes one argument. The thread that executes Command must be in the event loop, e.g. by means of vwait. Only one thread can "own" the exit procedure. If another thread has previously defined an exit procedure, then it will be overwritten. If Command is not specified, a two-element list is returned. The first element is the thread that will execute Command, the second element is the Command itself.

thread packages ?PackageList?

Defines or returns a list of all packages that are to be auto-loaded for each newly created thread. If PackageList is specified, it's elements must be the names of currently present packages. The mkThreads package itself does not need to be included in PackageList, its commands are already part of a new thread. Note that "package require" won't work in threads unless you specify the -init option with "thread create".

thread procs ?ProcList?

Defines or returns a list of all procedures set to be auto-loaded for each newly created thread. If ProcList is specified, it's elements must be the names of existing procedures. When a new thread is created, these procedures will be created in the thread's associated interpreter. This feature allows for a more "linear" code just as with e.g. C/C++, since procedures don't have to be redefined. Note that these procedures are physical copies of their originals. That is, if one of these procedures is overwritten later in the program, the other threads will not know about this.

thread id

Returns the thread identifier of the current thread, i.e. the thread that invokes this command.

thread exists ThreadId

Returns 1 if the thread specified by ThreadId exists, 0 otherwise.

thread names

Returns a list with all currently existing threads, including the main thread.

thread _register_ ChannelId ThreadId

Registers a channel specified by ChannelId for the thread specified by ThreadId. This is a very silly thing to do, since Tcl is not really ready for that. Please consider it as an experimental feature. Conceptually this is similar to the "interp transfer" command. Read the BUGS section below for important restrictions before you use this command!

thread _unregister_ ChannelId ?ThreadId?

Unregisters the channel specified by ChannelId. If ThreadId is specified, it unregisters the channel in that thread, otherwise it will unregister it in the current thread. Only those channels that have been registered by means of "thread _register_" must be applied. As for "thread _register_", this is purely experimental.

mutex create ?Name?

Creates a mutex and returns its name. If Name is specified, it will be used as the mutex' name, otherwise a string in the from mutex1 is returned.

mutex delete Name

Deletes the mutex specified by Name. This will fail if any threads are still trying to acquire that mutex.

mutex lock Name

Locks the mutex specified by Name. If this mutex has already been locked by another thread, then this thread will wait until it is unlocked by the other thread. If the same thread tries to lock a mutex twice, this command will return with an error.

mutex unlock

Unlocks the mutex specified by Name. If the thread is not the owner of that mutex, i.e. hasn't locked it before, then this command will return with an error.

mutex wait Name ?Milliseconds?

Waits for another thread to notify that mutex. If no timeout in form of milliseconds is specified, the thread will potentially wait forever. Otherwise it will wait the given amount of time only. The command will return 1 if a timeout occured and 0 otherwise. The mutex must be held in order to wait for notification (see the threads man page for detailed explanations).

mutex notify Name

Notifies any threads that are waiting for that mutex. It is recommended (but not enforced) to acquire the mutex before notifying on it. The waiting threads will be unblocked but not continue until the mutex is released (see the threads man page for detailed explanations).

mutex owner Name

Returns the ID of the thread that holds the mutex.

mutex lockers Name

Returns a list of IDs of those threads currently acquiring the mutex specified by Name, including the one that holds it.

mutex notifier Name

Returns the ID of the thread which most recently notified on the mutex specified by Name.

mutex exists Name

Returns 1 if the mutex specified by Name exists, 0 otherwise.

mutex names

Returns a list with all currently existing mutexes.

tvar set Name Value

Sets a thread variable specified by Name to the value specified by Value. If the variable does not exist, it will be created. Thread variables are common for all threads and provide a way of inter-thread communication (similar to static variables in C/C++).

tvar get Name

Returns the value of a thread variable specified by Name. If the variable does not exist, an error is returned.

tvar unset Name ?Name ...?

Unsets, i.e. deletes all thread variables specified by the given Names. If a variable does not exist then an error is returned.

tvar exists Name

Returns 1 if the thread variable specified by Name exists, 0 otherwise.

tvar names

Returns a list with all currently existing thread variables.

serialize ?Key? Script

Serializes a piece of code specified by Script. That is, it guarantees that no more than one thread will evaluate Script at any time. If several Scripts are associated with the same Key (an arbitrary string) then no more than one thread will be in ANY of these Scripts. If no Key is specified then internally an empty string will be used as a key. This command is an alternative to explicitely using mutexes and slightly more efficient.

 NOTES

Conditions

The mutex command internally combines a Tcl_Mutex and a Tcl_Condition (see the threads man page). Dynamically created Tcl_Conditions, either on the heap like with the mutex command or on the stack, seem to leave some memory behind, once they have been used. This even occurs when they are properly finalized and freed up. One Tcl_Condition is also used for each created thread. It is therefore recommended a) to create a limited amount of mutexes and reuse them, and b) to either create a limited amount of threads and reuse them by sending them scripts, or to create as many threads as desired but not to wait for them (i.e. don't use their Tcl_Condition).

Package Unloading

When this package is unloaded, e.g. if it has been required in an additional interpreter and then the interpreter is deleted, it is strongly recommended not to have any more threads running.

Standard Channels

In Wish for Windows, commands that use the standard channels STDIN, STDOUT and STDERR (such as puts, gets and read) don't work properly within threads. Populating the three standard channels to every new thread (using Tcl_GetStdChannel and Tcl_RegisterChannel) doesn't help but merely creates another memory leak. This leak is also created whenever a thread does e.g. a "puts", because it will automatically create the three standard channels (which did not exist before). This goes for tclsh as well as for wish.

Sharing Channels

One must be very careful when using the "thread _register_" and "thread _unregister_" commands to share or transfer a channel to another thread, since Tcl's internal linked lists do not really allow to simply call Tcl_RegisterChannel in order to populate a channel to another thread. Depending on the platform different effects might occur, and crashes are likely. Therefore this feature is an experimental one. Some tests under Windows NT and Linux 2.2 showed that

(Yes, it is indeed possible to write a TCP server that distributes incoming connects to separate threads, which later terminate correctly, don't create a memory leak, and leave an intact channel list behind...)

 EXAMPLES

Try this in the Tcl shell tclsh:

  % package require mkThreads
  1.2
  % thread create { after 1000; puts "Thread [thread id] terminates" }
  440
  % Thread 440 terminates
  % thread create
  250
  % thread eval -wait 250 { return RESULT }
  RESULT
  % proc p { args } { puts "Args: $args" }
  % thread eval 250 { after 2000; set AnError } p; after 4000 { set x 0 }; vwait x
  Args: 1 {can't read "AnError": no such variable} NONE {can't read "AnError": no such variable while executing "set AnError "}
  % proc e { tid } { puts "Thread $tid exited" }
  % thread onexit e
  % thread eval 250 { after 2000; thread exit }; after 4000 { set x 0 }; vwait x
  Thread 250 exited
  %
  

 INSTALLATION

 General

mkThreads is written in C and comes with a DLL for Windows. On Unix, the package needs to be compiled into a shared library first (see below). mkThreads works with Tcl/Tk version 8.1 and higher. It has been stubs-enabled with version 8.2.

Beginning with Tcl 8.3.1, mkThreads 1.2 incorporates the revised thread API and is therefore stubs-enabled. You still should be able to compile and run it for all Tcl versions beginning with 8.1 (where thread support was initially introduced).

Important: Your Tcl/Tk version installed on your computer must have been compiled with thread support. To find out, check for the existence of the global variable "tcl_platform(threaded)". If it does not exist then your Tcl/Tk has not been compiled with thread support. See the Scriptics web site on how to compile it with thread support.

To install, place the directory "mkThreads1.2" in one of the directories contained in the global Tcl variable "auto_path". For a standard Tcl/Tk installation, this is commonly "c:/program files/tcl/lib" (Windows) and "/usr/local/lib" (Unix).

Compiling

To compile the package, just provide the correct path to "tcl.h" and link against "tcl8x.lib" (Windows) or "libtcl8x.so" (Unix) respectively. If you use stubs, define USE_TCL_STUBS and link against "tclstub8x.lib" (Windows) or "libtclstub8x.a" (Unix) instead.

For Visual C++, the following command should work:

  cl /I c:/progra~1/tcl/include /D USE_TCL_STUBS /c mkThreads12.c
  link c:/progra~1/tcl/lib/tclstub83.lib /dll mkThreads12.obj

On Linux 2.2, this here works fine:

  gcc -shared -DUSE_TCL_STUBS -ltclstub8.3 -o mkThreads12.so mkThreads12.c

Test

Test the installation by opening a tclsh or wish and entering "package require mkThreads". The string "1.2" should appear. If it fails, "cd" into the directory "mkThreads1.2" and load it directly with "load ./mkThreads12.dll" (Windows) or "load ./mkThreads12.so" (Unix). If no error occured, it succeeded and something must be wrong with the location of "mkThreads1.2".

 CHANGES

Changes from 1.0 to 1.2

 AUTHOR

Michael Kraus
mailto:michael@kraus5.de
http://mkextensions.sourceforge.net