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:
-
package require Thread
-
socket -server NewConnection 7777
-
proc NewConnection {channel peerhost peerport} {
-
fconfigure $channel -buffering none
-
set t [thread::create {
-
proc HandleConnection {channel} {
-
while {![eof $channel]} {
-
if {[gets $channel line] < 0} {
-
continue
-
}
-
puts $channel $line
-
}
-
close $channel
-
thread::exit
-
}
-
thread::wait
-
}]
-
after 0 [list HandOver $t $channel]
-
return
-
}
-
proc HandOver {thread channel} {
-
thread::transfer $thread $channel
-
thread::send -async $thread [list HandleConnection $channel]
-
return
-
}
-
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.