Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In Tcl, a thread owns its interpreter: other threads must not call that interpreter directly. Use Tcl’s Thread package to create workers and send them scripts with thread::send. Each worker runs its own interpreter, and you can choose whether to wait for a result, send work asynchronously, transfer an I/O channel, or coordinate access to a genuinely shared resource.

How Tcl threads and interpreters work

The Thread extension follows a simple ownership rule: an OS thread can have one or more Tcl interpreters, but each interpreter should be used only by the thread that created it. Treat an interpreter and its variables, commands, and application state as belonging to one thread. Calling into another thread’s interpreter directly is not a valid way to share Tcl state.

Instead, send a script to the thread that owns the interpreter. That keeps Tcl execution in the owning thread while letting other threads request work or receive results. In practice, this makes message passing the natural starting point for Tcl concurrency.

Create a worker and send it work

Load the Thread package in the application, create a joinable worker, define worker-side procedures in that worker’s interpreter, and send it a script. A worker created without a startup script runs an event loop so it can receive messages.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
package require Thread

# Create a worker that the application can join during shutdown.
set worker [thread::create -joinable]

# Define the procedure in the worker's interpreter.
thread::send $worker {
    proc square {n} {
        expr {$n * $n}
    }
}

# A synchronous send returns the worker's result.
set answer [thread::send $worker [list square 12]]
puts $answer

The result printed is 144. The worker’s square procedure exists in the worker interpreter, not the caller’s interpreter. Use a list to construct a script when inserting values into a command; this preserves Tcl’s argument boundaries rather than relying on string concatenation.

Load packages and define procedures that the worker needs in its own interpreter. Keep its state there as well, and send it requests to read or change that state.

Choose synchronous or asynchronous messaging

Approach What the caller does Use it when What to plan for
Synchronous thread::send Waits until the target evaluates the script and returns its result. The caller needs the result before continuing, or the request is part of a simple step-by-step operation. The caller is blocked while the target handles the request. Avoid designs in which each side waits synchronously for the other, which can deadlock.
Asynchronous thread::send -async Submits the script and returns without waiting for its result. The caller can continue while the worker performs work. The send itself does not give the caller the eventual result. Arrange a separate response message or callback, and ensure the receiving thread processes events.
Channel transfer Moves an I/O channel to another thread rather than sharing the interpreter that opened it. A worker should perform I/O on a channel and the application can hand off channel ownership. After transfer, design the code so the channel is used by its new owning thread, not concurrently by both threads.

For example, an asynchronous job needs an explicit result path: the worker can send a completion message back to the caller or notify a callback. The caller must be able to process that message; asynchronous submission alone is not a completion notification. Keep synchronous request-and-response for cases where blocking is acceptable and the result is needed immediately.

Keep the target thread able to receive messages

thread::send depends on the target thread processing events. A worker created without a startup script runs an event loop automatically. If you create a thread with startup work or otherwise control its execution, that code must eventually enter an event-driving command such as thread::wait, vwait, or another command that processes events.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A thread that never services its event queue cannot handle incoming sends while it is occupied elsewhere. Design long-running worker procedures so they do not permanently prevent the worker from returning to event processing if it still needs to receive requests.

Transfer channels for I/O instead of sharing interpreters

Tcl supports transferring an I/O channel to another thread. This is useful when, for example, a worker should perform I/O on a channel without asking another thread to operate the channel through its interpreter. The Thread package provides thread::transfer for channel handoff.

Make the handoff part of the design: the receiving thread should become responsible for using the transferred channel, and the sending thread should stop treating it as its own. Channel transfer moves I/O responsibility; it does not make the two interpreters interchangeable or permit both threads to use one interpreter.

Use mutexes and condition variables only for shared resources

Message passing is usually the simpler default when each worker owns its state. Use a mutex when threads truly need coordinated access to a resource, and a condition variable when one thread must wait for another to signal that a condition has changed. The Thread package supplies mutex and condition-variable commands for this kind of synchronization.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Prefer messages when one worker can own a value or resource and other threads can request changes from it.
  • Use a mutex to protect a resource that must be accessed by more than one thread. Keep the locked section limited to the operations that require protection.
  • Use a condition variable when a thread needs to wait for a state change, rather than repeatedly checking the state.

Synchronization does not change interpreter ownership. A mutex can coordinate access to an appropriate shared resource; it does not make it valid to evaluate Tcl commands in another thread’s interpreter.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Shut down workers deliberately

For a joinable worker, orderly shutdown means arranging for the worker to finish and then joining it. In the example above, after the application has no more work for the worker, it can request the worker to exit and wait for completion:

thread::send -async $worker {thread::exit}
thread::join $worker

The asynchronous send lets the worker process its exit request; thread::join waits for the joinable thread to end. In an application with pending jobs or a completion-message protocol, stop accepting new work and decide what happens to outstanding work before requesting exit. Do not assume an asynchronous send means the requested script has already run.

The Thread package also provides thread::preserve and thread::release for managing a thread’s lifetime. Use the lifecycle approach that matches how the thread was created and managed; do not mix cleanup expectations for a joinable worker with a separate preserve/release design without checking the Thread command semantics for the deployed package.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check thread support in the Tcl runtime you deploy

The Tcl core became thread-safe with Tcl 8.1, and Tcl multithreading support is enabled by default starting with Tcl 8.6. “Enabled by default” does not guarantee that every deployed Tcl build or package installation has the configuration your application needs. Verify the actual runtime and Thread package on the system where the application will run.

Thread safety in the core is not the same as permission to share an interpreter: the single-owning-thread rule still applies. The Thread package supplies script-level worker creation, messaging, lifecycle commands, mutexes, and condition variables; Tcl’s C API provides lower-level thread creation, event-queue operations, mutexes, condition variables, and thread-local storage.

A reference for deeper Tcl threading work

Practical Programming in Tcl and Tk, 4th Edition by Brent Welch and Ken Jones is a 2003 Pearson book whose catalog contents cover thread-enabled interpreters, creating and joining threads, synchronous and asynchronous messaging, preserving and releasing threads, error handling, shared resources, channel transfer, mutexes, condition variables, thread pools, and Thread package commands. Its publication date makes it a historical reference rather than a statement about current package availability or current Tcl versions.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.