Threads vs. events

The following came from the not uncommon Question/Answer pair

   Q: I Want Threads
   A: You Have An [Event] Loop

AW: But I have multiple processors/cores.

mailto:[email protected] (Uwe Klein) wrote in comp.lang.tcl:

Your answer says only that tcl has an event loop, but gives no explamation as to the differences / advantages.

1st Try:


see if any in a list of jobs have to be done and process them. loop till dead.


In a threaded program one will do each "primitive" loop of action in its own thread.

 thread1 { block in read ; read file ; process read ; 
                                signal whats been done }
 thread2 { block on user input ; process user input ; 
                                signal for crt update  }
 thread3 { ...

Where threads interact they have to be synced somehow.


In event-oriented programming one will set up an event handler for anything that should / will happen.

 event1 { fileevent readable datafile { do what must be done 
                        i.e. put data into dataarray } }
 event2 { writing to dataarray { compute depending values } }
 event2a { writing to dataarray { update crt } }
 event3 { button EXIT { clean up and exit }
 event4 { ...

Generated events are queued and then processed by the event loop.

If all dependencies are described correctly through "trace variable" , "fileevent", "widget -command" , .. any internal/external stimulus will cascade down its described dependecies. Think of an event-looped program as something equivalent to a Makefile for your functional requirements.

Caveat: if your code contains "computing monoblocs" your program becomes unresponsive.

AMG: Try moving these long calculations into child processes. These programs take all their input from stdin or the command line arguments, write their results to stdout, and abort by writing to stderr and [exit]ing nonzero. If you want progress feedback, have them (optionally?) write that to stdout as well, and update your GUI when progress data is received. Run the child process with [set $chan [open |[list program arg1 arg2 ...] a+]]] and give it a [fileevent $chan readable] handler. [puts $chan] all the data it needs. Such programs can do one thing and quit, or stay running as long as your program runs. (Example: a DNS lookup program, to dodge blocking gethostbyname().)

These programs can be written in any language (be open to the possibility that another language, say C, might be more appropriate, like in the DNS case), which is a nice feature, and also a good demonstration of Tcl's power as an integration (glue) language. Also these programs can be run outside of your script; maybe they're useful all by themselves, as command line tools.

slebetman 3 Jan 06: AMG's "open" method unfortunately doesn't work on Windows for long running programs since you'll only get the program's stdout at the moment the program exits (hence no parent-child communication). This 'bug' currently affects all versions of Windows up to WinXP.

UK 3 Jan 06: i found blt's bgexec more elegant than either exec or pipe, it is available on win. I don't know if it has the same issues ( i think not).

Sarnold 2 Jul 06: I can give a short example of such event-oriented programming. This was the solution to keep my GUI alive during long computations of the Mancala game, allowing the player to cancel the game.

I could not do that with Threads, although I thought it would be nice. Each time the computer was 'thinking', it freezed the whole GUI.

Bonus: when the user cancels a computation, the background process is killed -- so it can not waste CPU time anymore.

 catch {package require Tclx}
 proc exe-refresh {chan} {
     global BIN GUESS
     if {$BIN eq $chan} {return}
     if {$BIN ne ""} {
         # needs TclX
         catch {kill [pid $BIN]}
         # no, close does not terminate the attached process, at least on Win XP
         catch {close $BIN}
     set BIN $chan
     set GUESS ""
 proc eventually {chan} {
     global BIN GUESS
     # if the current channel has been cancelled,
     # close the channel, free the resource
     if {$chan ne $BIN} {
         catch {close $chan}
     # read until the end of the process
     # stores the chosen pit into a global variable
     append GUESS [read $chan]
     if {[eof $chan]} {close $chan}

 proc make-bin-best-move {board player nest awelemode islight} {
     set fd [open [concat | cmancala.exe $board $player $nest $awelemode $islight]]
     fileevent $fd readable [list eventually $fd]
     global GUESS BIN
     exe-refresh $fd
     tkwait variable GUESS
     set pit $GUESS
     if {$BIN ne $fd} {error "operation cancelled by user"}
     set BIN ""
     set GUESS ""
     # restores the config
     if {![string is integer $pit]} {error "internal error: integer expected, got $pit"}
     return $pit
     # somewhere in a Tk callback
     # cancels the move and stops the background process
     exe-refresh ""
 # init
 set BIN ""
 set GUESS ""

Note that the authors of "Why Events Are A Bad Idea" [L1 ] explicitly claim compatibility with JOs' recommendations; their analysis of threading focuses on certain high-performance servers, and moreover emphasizes the well-documented duality between concurrency concepts.

UK the central isssue with events is that they can/will not utilise SMP Systems. An interesting idea would be to have easily migratable interp's:

 package require mip ;# the imaginary migratable interp package
 set interp [ mip::interp create ]
 mip::migrate -tothread $interp
 mip::migrate -toremote $rhost:$rport $interp

NEM notes that separate processes can take advantage multiple processors/cores just as easily as multiple threads. So, e.g. farming out intensive processing to background tasks using exec or open as suggested above would help with this. I'd also note that it is the shared state aspect of threads that make them particularly tricky to program with. Something like Erlang's message-passing lightweight processes are much simpler (and would probably work very nicely with a Tcl-like event model).