[ Team LiB ] Previous Section Next Section

Getting Started with the Thread Extension

You start a thread-enabled tclsh or wish the same as you would a non-threaded tclsh or wish. When started, there is only one thread executing, often referred to as the main thread, which contains a single Tcl interpreter. If you don't create any more threads, your application runs like any other single-threaded application.

graphics/common_icon.gif

Make sure that the main thread is the last one to terminate.


The main thread has a unique position in a multi-threaded Tcl script. If it exits, then the entire application terminates. Also, if the main thread terminates while other threads still exist, Tcl can sometimes crash rather than exiting cleanly. Therefore, you should always design your multi-threaded applications so that your main thread waits for all other threads to terminate before it exits.

Before accessing any threading features from your application, you must load the Thread extension:

package require Thread

The Thread extension automatically loads itself into any new threads your application creates with thread::create. All other extensions must be loaded explicitly into each thread that needs to use them. The Thread extension creates commands in three separate namespaces:

  • The thread namespace contains all of the commands for creating and managing threads, including inter-thread messaging, mutexes, and condition variables.

  • The tsv namespace contains all of the commands for creating and managing thread shared variables.

  • The tpool namespace contains all of the commands for creating and managing thread pools.

Creating Threads

The thread::create command creates a new thread containing a new Tcl interpreter. Any thread can create another thread at will; you aren't limited to starting threads from only the main thread. The thread::create command returns immediately, and its return value is the ID of the thread created. The ID is a unique token that you use to interact with and manipulate the thread, in much the same way as you use a channel identifier returned by open to interact with and manipulate that channel. There are several commands available for introspection on thread IDs: thread::id returns the ID of the current thread; thread::names returns a list of threads currently in existence; and thread::exists tests for the existence of a given thread.

The thread::create command accepts a Tcl script as an argument. If you provide a script, the interpreter in the newly created thread executes it and then terminates the thread. Example 21-1 demonstrates this by creating a thread to perform a recursive search for files in a directory. For a large directory structure, this could take considerable time. By performing the search in a separate thread, the main thread is free to perform other operations in parallel. Also note how the "worker" thread loads an extension and opens a file, completely independent of any extensions loaded or files opened in other threads.

Example 21-1 Creating a separate thread to perform a lengthy operation
package require Thread

# Create a separate thread to search the current directory
# and all its subdirectories, recursively, for all files
# ending in the extension ".tcl". Store the results in the
# file "files.txt".

thread::create {
   # Load the Tcllib fileutil package to use its
   # findByPattern procedure.

   package require fileutil

   set files [fileutil::findByPattern [pwd] *.tcl]

   set fid [open files.txt w]
   puts $fid [join $files \n]
   close $fid
}

# The main thread can perform other tasks in parallel...

If you don't provide a script argument to thread::create, the thread's interpreter enters its event loop. You then can use the thread::send command, described on page 328, to send it scripts to evaluate. Often though, you'd like to perform some initialization of the thread before having it enter its event loop. To do so, use the thread::wait command to explicitly enter the event loop after performing any desired initialization, as shown in Example 21-2. You should always use thread::wait to cause a thread to enter its event loop, rather than vwait or tkwait, for reasons discussed in "Preserving and Releasing Threads" on page 330.

Example 21-2 Initializing a thread before entering its event loop
set httpThread [thread::create {
    package require http
    thread::wait
}]

graphics/common_icon.gif

After creating a thread, never assume that it has started executing.


There is a distinction between creating a thread and starting execution of a thread. When you create a thread, the operating system allocates resources for the thread and prepares it to run. But after creation, the thread might not start execution immediately. It all depends on when the operating system allocates execution time to the thread. Be aware that the thread::create command returns when the thread is created, not necessarily when it has started. If your application has any inter-thread timing dependencies, always use one of the thread synchronization techniques discussed in this chapter.

Creating Joinable Threads

Remember that the main thread must be the last to terminate. Therefore you often need some mechanism for determining when it's safe for the main thread to exit. Example 21-3 shows one possible approach: periodically checking thread::names to see if the main thread is the only remaining thread.

Example 21-3 Creating several threads in an application
package require Thread

puts "*** I'm thread [thread::id]"

# Create 3 threads

for {set thread 1} {$thread <= 3} {incr thread} {
   set id [thread::create {

      # Print a hello message 3 times, waiting
      # a random amount of time between messages

      for {set i 1} {$i <= 3} {incr i} {
         after [expr { int(500*rand()) }]
         puts "Thread [thread::id] says hello"
      }

   }] ;# thread::create

   puts "*** Started thread $id"
} ;# for

puts "*** Existing threads: [thread::names]"

# Wait until all other threads are finished

while {[llength [thread::names]] > 1} {
   after 500
}

puts "*** That's all, folks!"

A better approach in this situation is to use joinable threads, which are supported in Tcl 8.4 or later. A joinable thread allows another thread to wait upon its termination with the thread::join command. You can use thread::join only with joinable threads, which are created by including the thread::create -joinable option. Attempting to join a thread not created with -joinable results in an error. Failing to join a joinable thread causes memory and other resource leaks in your application. Example 21-4 revises the program from Example 21-3 to use joinable threads.

Example 21-4 Using joinable threads to detect thread termination
package require Thread

puts "*** I'm thread [thread::id]"

# Create 3 threads

for {set thread 1} {$thread <= 3} {incr thread} {
   set id [thread::create -joinable {

      # Print a hello message 3 times, waiting
      # a random amount of time between messages

      for {set i 1} {$i <= 3} {incr i} {
         after [expr { int(500*rand()) }]
         puts "Thread [thread::id] says hello"
      }

   }] ;# thread::create

   puts "*** Started thread $id"

   lappend threadIds $id

} ;# for

puts "*** Existing threads: [thread::names]"

# Wait until all other threads are finished

foreach id $threadIds {
   thread::join $id
}

puts "*** That's all, folks!"

graphics/common_icon.gif

The thread::join command blocks.


Be aware that thread::join blocks. While the thread is waiting for thread::join to return, it can't perform any other operations, including servicing its event loop. Therefore, make sure that you don't use thread::join in situations where a thread must be responsive to incoming events.

    [ Team LiB ] Previous Section Next Section