Concurreny in Tcl: Weaving Threads

Introduction

In the previous article we talked about pseudo-concurrency through events and mentioned threads as one possible solution to some of their limitations. This second article expands on that.

Threads compared to Events

First, in contrast to events, only the C level foundations are built into the core. Script access to threads requires a package: Thread.

The model espoused by both foundation and package is the so-called “apartment” model. Tcl treats threads like separate processes, which just happen to be run in the same memory space. They can talk to each other through what is essentially an explicit message-passing API.

The nice thing about this is that all the unpleasantness of race conditions and such for access to shared memory are contained in the implementation of the message-passing commands. To the scripts running in the various threads there is no shared memory. That gets rid of a whole class of potential problems outright.

Example: Thread-based Network Server

Using the network server of the previous article as example, this is how it would implemented with threads, mostly:

  1. package require Thread
  2. socket -server NewConnection 7777
  3. proc NewConnection {channel peerhost peerport} {
  4.     fconfigure $channel -buffering none
  5.     set t [thread::create {
  6.         proc HandleConnection {channel} {
  7.             while {![eof $channel]} {
  8.                 if {[gets $channel line] < 0} {
  9.                     continue
  10.                 }
  11.                 puts $channel $line
  12.             }
  13.             close $channel
  14.             thread::exit
  15.         }
  16.         thread::wait
  17.     }]
  18.     after 0 [list HandOver $t $channel]
  19.     return
  20. }
  21. proc HandOver {thread channel} {
  22.     thread::transfer $thread $channel
  23.     thread::send -async $thread [list HandleConnection $channel]
  24.     return
  25. }
  26. vwait _dummy_variable_

While there is still a bit of event management in waiting for new connections, their handling is shifted into threads. One thread is created per connection, with the necessary handler procedure written to use blocking IO calls in a regular fashion.

The interesting points in the script are the ‘thread::create‘ command to make and initialize a new thread, the ‘thread::transfer‘ used to shift the new socket from the main thread into the worker thread, and the ‘thread::send‘ to run scripts in other threads, with ‘thread::wait‘ waiting for incoming requests made by ‘thread::send’.

Note that this code is simplicistic, killing the thread of a connection whenever the user is done with it. In a proper application there would very likely be some sort of thread-pool keeping released threads around for re-use by new connections, to reduce the overhead associated with thread creation and destruction.

Outlook

Our examples so far have had lots of concurrency, but with no need for communication between the parts at all. Now that we know about the principle of bringing concurrency to Tcl processes, only one issue is left to discuss: How can we get the multiple parts of a concurrent application to talk to each other? We’ll cover that in the next post.

Title image courtesy of MBatty on Pixabay.

Andreas Kupries

Andreas Kupries

Andreas Kupries is a senior Tcl developer at ActiveState where his focus is the ActiveTcl distribution. He also works on the Tcl Dev Kit component of ASPN Tcl. Andreas is a member of the Tcl Core Team. He's the Tcllib release manager and has authored more than 25 Tcl modules and extensions! He has a Master in Computer Science from the RWTH Aachen.