Version 75 of Tcl and threads

Updated 2008-08-26 10:58:43 by arjen

see thread for links to documentation on the Tcl script-level usage of threads.

Explain what threads are.

Explain why threads are sometimes useful.

Explain why threads are sometimes critical or required.

Explain threading vs. thread-safety.

See http://www.tcl.tk/doc/howto/thread_model.html

Note: There is very little code out there in the "real world" that is actually thread-safe. The gnu C++ lib has unsafe code! Thread behaviour is treacherously platform dependent.

Where you may write a program that makes use of threads and seems to be well behaved, it is possible that even on the same platform the behaviour will change over time in a long running thread. The handling of exceptions from threads is, IMHO, broken on all platforms other than Solaris on Sparc. The glibc folks are working overtime to remedy this by fixing thread safety issues, and it is *possible* that things will improve significantly by the end of 2002.

Windows users are on their own. Microsoft could whimsically implement any old craziness and you'd never know.

I am not referring to the Tcl threading model, which is (apparently) designed to mitigate the effects of non-reentrant code (I admit to not being up on this, we use our own threading model by necessity.) I am referring to OS threads and their general use in Tcl extensions.

Perhaps I am completely out here on my own and everyone else is talking specifically about "Tcl threads". If so then anyone who cares to may remove these comments.

AW This rather generalizing statement dating back to before 2002 by ?? would seem to imply that no one uses threads because it's not possible. This is not true at all. Take a look at the processes on your computer and the number of threads in each as an example. E.g. on my system I see firefox using 14 threads currently, to name just one well known app. This is almost a necessity already, and many think it will be the only way forward soon, see e.g. "The free lunch is over" by Herb Sutter [L1 ].


Explain how Tk isn't thread-safe [is it Tk that isn't thread safe, or is it the windowing system underneath Tk?], but GUIs never are, and no one cares (AW actually, we do, see my debugging example below), because they always have a single GUI thread, with others as helpers. Windows specifically only allows a window to receive events from the thread that created the window (can that be true?!?).

One should not expect to use a tcl library compiled with thread support and Tk without thread support compiled in. What you need to use is a tcl and tk both with thread support compiled in - but then be careful to not use tk in more than one thread at a time.


Explain threading as programming model most like (modified) assembly line.


David Gravereaux is expert in Tcl threading. Also, Jean-Luc Fontaine has been thinking about this deeply for his TclPython work.

Way back in July 1999, Alexandre Ferrieux summed it up nicely ...

  This is related to something I've realized only recently with the
  Tcl threading model: a purposeful, strong isolations of threads. At
  the beginning, I believed that "at most one thread by interp" was
  a dirty hack to hide bad reentrancy in the core. Now I have come to
  understand that instead, it pushes forward a new (as compared to C, 
  as you mentioned) and also very "Tclish" style: basically, Tcl 
  threads are nearly as isolated as *processes*, which is nice 
  because it means all the modularity and atomic-testing we want, 
  without the fork/exec and address space switch overhead !'

For the full posting see [L2 ]


Explain Tcl (and Tk) generation options.


[L3 ] remarks on history and use of Tcl, particularly as compared to Perl and Python. It also references John Ousterhout's Thread paper [L4 ] [while the previous link currently does not appear to be available, http://home.pacbell.net/ouster/threads.pdf is a PDF of the invited Talk that Dr. O gave which may correspond in some way to what was previoussly being referenced.] which argues for event-based programming rather than thread programming.

TV (Nov 6 '03) It's understandable, since threads as opposed to processes normally would offer leightweightness, which under tcl is probably not so important, and shared (global) variables and resources, which is however also possible on a per event switching basis. Note that synchronization usually takes place anyhow based on file or graphics events, though not sctrictly necessarily. A usual application for windows' threads I guess is graphics related (e.g. dialogs) which under tk is relatively seperated.


Point to books, tutorials, online references, etc. on best practices for thread design and programming considerations.


Point to critical info on testing (and debugging!) threaded apps.


Extensions and threading ...

  • The Thread extension (at SourceForge, under the tcl project [L5 ]) lifts the threading capabilities of the Tcl C-API into the scriptlevel.

Remember: "You may have more than one interp per thread, but never is one interp shared _across_ threads," as David Gravereaux posted to comp.lang.tcl.


Until the generation procedures are cleaned up, workers in this area will need to be familiar with "-DTCL_THREADS=1".


History of Tcl threads: Steve Jankowski released "mttcl" [L6 ] in 19??. D. Richard Hipp ... Jim Davidson's work for NaviServer ...


Zoran Vasiljevic makes the interesting point that "If you have many threads, load Tcl interpreter in each of them and then load a module in each interpreter, then your references to a global hash table will definitely go to the same table instance. Which means you need to protect access to this table with a mutex.

If you need separate tables, i.e. you need not share hash-table data between threads, then you should put the table in thread-specific data structure (TSD), so each thread gets its own copy You would also like to register some thread-exit handler which takes care about finalizing those tables on thread exit, otherwise you'll lose memory."

JMN What sort of things will create 'a global hash table'? I see from another page that arrays are 'implemented internally by hash tables', so does this mean I have to do something fancy just to access a global array from threads? Does this even apply to using threads at the TCL scriptlevel? If not, perhaps it'd be possible for someone knowledgeable to separate out the information on this page that pertains to the C-API to keep such terrifying statements away from those who just want to use the Thread package from script. Ok.. perhaps there are some good reasons to keep people just a little wary of threads - but I'm not sure the above belongs right next to the nice welcoming 'cookbook approach' below.


A Cookbook Approach

How to use the Thread2.1 package

 #!/bin/sh
 #\
 exec tclsh "$0" "$@"

 package require Thread 2.1

 #
 # Start a thread. If you will be running many long running commands
 # at the same time, just create multiple threads identical to this one, and
 # keep track of the threadID's.
 #
 set threadID [thread::create {
    #
    # From here to the 'thread::wait' statement, define the procedure(s)
    # that will be called from your main program (which, btw, is thread #1)
    #
    # In this case, I've defined a simple procedure that executes the command
    # passed into this thread. This command could be an external program or
    # long running tcl command.
    #
    # The result is then sent back to the main program (thread 1) via a call
    # to a procedure in thread 1. In this example, it's a procedure defined
    # at the global level.
    #
    # The 'thread::wait' is required to keep this thread alive indefinitely.
    #
    proc runCommand { ID command } {
        set result [eval $command]
        eval [subst {thread::send -async $ID \
                {::printResult [list $result]}}]
    }
    thread::wait
 }] 

 #
 # Here is the procedure that gets called from the thread when the thread
 # has completed its work.
 #
 proc printResult { result } {
    puts $result
    exit
 }

 proc passTheTime { } {
    puts [clock format [clock seconds]]
    after 1000 passTheTime
 }

 #
 # Here we define a sample command, and pass it into the previously started
 # thread.
 #
 set commandString "exec du -sk /usr/local"
 eval [subst {thread::send -async $threadID \
        {runCommand 1 [list $commandString ]}}]

 #
 # In this example, lets just pass the time until the thread is complete
 #
 passTheTime
 vwait forever

Marty Backe 21 Feb 2002

AW The above example also provides a nice way to get results from a worker thread back to your main program: have the worker thread send them back to the main thread in the same way you send work to the worker threads. Also, note that there is an equivalent way of making a command with replaced params, instead of eval [subst ...] :

  thread::send -async $threadID \
        [list runCommand 1 [list $commandString ]]

Also note that the main thread is not '1' on windows, so you'll need to pass the main thread ID to the worker threads.


Zoran Vasiljevic has posted an example of a logger thread and worker threads on c.l.t. (add google groups reference here): Sharing a common logger thread example


tclguy "just wanted to note that I have found the intermixing of thread-enabled and disabled Tcl and extensions to actually be acceptable in most contexts where only one thread was used, or if other Tcl-level threads were created, they were only used for Tcl."

Which statement, while true, requires a lawyer to determine whether it has any significance relative to a project using threads. It sounds like you're saying "if you don't use threads intentionally, the multi-threading Tcl will not bite you otherwise". Is that right? I mean, is that what you meant to say; I don't think you actually know if it's right.


CMcC Each tcl core subsystem has Thread Specific Data


[Do we make it clear that different threads can't share an interp?]


elfring 2003-10-19 There are two thread packages. Which one do you prefer?


elfring 2003-11-05 Are you interested in the improvement to support all synchronization primitives in these libraries [L7 ]? Would you like to cooperate with the development of a TIP?

elfring 2003-11-06 Are you prepared for thread safety [L8 ]?


See Thread-safe Tcl Extensions for a list of extensions believed to be safe for use in multi-threaded Tcl applications.


One curiousity--even annoyance--of threads prior to the 2.6 release is that, while threads can communicate variables and other resources, they do not have an easy way to share proc definitions. The main alternatives are to:

  • re-source in definitions from the file system; or
  • stuff a proc definition in a variable, send the variable, and eval

2.6 will provide [ttrace::eval], as in

    ttrace::eval source myfile.tcl

Commands proper? For the foreseeable future, each thread must load its own.


Some consolidated rules for embedding Tcl in a threaded application:

  • You can NEVER use the same interpreter from more than one thread
  • If you only have one Tcl Interpreter:
  • You can use either Unthreaded or Threaded Tcl
  • No Big Global Mutex Big Global Mutex is required for Unthreaded Tcl Build (Never required for Threaded Tcl build)
  • If you have Multiple Tcl Interpreters:
  • If you are using an Unthreaded Tcl Big Global Mutex Big Global Mutex is required for ALL calls to functions in libtcl
  • If you are using a Threaded Tcl, no mutex locks are required

Example of all three situations (Single Interp, Unthreaded/Threaded Tcl, Multiple Interps Unthreaded Tcl, Multiple Interps Threaded Tcl) can be found in PNFQ PNFQ, assuming it is released.


AW : Debugging threads is non-obvious at first. This does work under tclsh, but not under wish (the puts from inside the thread is not displayed in the console):

  package require Thread
  catch {console show}
  set ::gThread [thread::create {thread::wait} ]
  puts "created thread $::gThread"
  proc test { } {
    puts "test starting"
    thread::send -async $::gThread {puts [clock seconds]}
    after 2000 test
    puts "test ending"
  }

  test
  puts "started first test"

  #only needed for tclsh, to keep the interpreter alive and keep the event loop running
  vwait forever

The reason is described here [L9 ]. Replacing the thread command by:

    thread::send -async $::gThread { tk_messageBox -message "[clock seconds]" -title "t" -type ok }

Also does not work under wish (messagebox is never shown). The reason for that becomes obvious when you implement a thread error proc:

  proc ThreadError { thread_id errorInfo } {
    puts "Error in thread $thread_id. Error: $errorInfo"
  }  
  thread::errorproc ThreadError

There is no tk in the thread's tcl interpreter. (This is probably due to tk not being thread safe?)

So finally, this works in both tclsh and wish:

  set ::a 0

  package require Thread
  catch {console show}

  set ::gThread [thread::create {thread::wait} ]
  puts "created thread $::gThread"
  proc test { } {
    puts "test starting $::a"
    thread::send -async $::gThread { return [clock seconds] } ::a
    after 2000 test
    puts "test ending $::a"
  }

  test
  puts "started first test"

  #only needed for tclsh, to keep the interpreter alive and keep the event loop running
  vwait forever

Alternatively, you can use the technique under 'A Cookbook Approach' above to send a command back to your main thread.


Also see "Concurrency concepts".


AM (26 august 2008) Here is a Modest tool for simulating threads, its purpose is to provide insight in what a multithreading program (or the relevant synchronising parts of it) is doing. Mind you: a very modest tool.