Here are two cut-down examples which show what I'm trying to do:
```
# eg #1
package require thread 3
proc main {} {
set names [list one two three four]
set tids [list]
foreach name $names {
set tid [thread::create -joinable]
append tids $tid
thread::send $tid [list process $name]
}
foreach tid $tids { thread::join $tid }
}
proc process name { puts "processing $name in thread [thread::id]" }
main
```
The error I get (Tcl 9.0.2) is:
```
invalid command name "process"
while executing
"process one"
invoked from within
"thread::send $tid [list process $name]"
(procedure "main" line 7)
invoked from within
"main"
(file "./t1.tcl" line 17)
```
I want the program to execute one custom global command with one argument but although I can pass the argument (I think), I can't access the global function
called `process`. (In my real code the `process` command calls several other global commands.)
I tried another way, which fails differently:
```
# eg #2
package require thread 3
proc main {} {
set names [list one two three four]
set tids [list]
foreach name $names {
set tid [thread::create -joinable]
append tids $tid
tsv::set shared name $name
thread::send $tid {
puts "processing [tsv::get shared name] in thread [thread::id]"
}
}
foreach tid $tids { thread::join $tid }
}
main
```
This one outputs:
```
processing one in thread tid0x7d0aad9ff640
processing two in thread tid0x7d0aad1fe640
processing three in thread tid0x7d0aac9fd640
processing four in thread tid0x7d0a9ffff640
```
much as expected but (1) it isn't using my own global `process` command and (2) it doesn't terminate! It just hangs at the end.
set tid [thread::create {
proc process {
...
}
thread::wait
}
]
And you need to define it in each of the threads you create.
-e
* Mark Summerfield <[email protected]>
| proc main {} {
| set names [list one two three four]
| set tids [list]
| foreach name $names {
| set tid [thread::create -joinable]
| append tids $tid
Use lappend here, not append, otherwise the 'foreach' below waiting for
the threads will not work as expected.
| tsv::set shared path $::APPPATH
| tsv::set shared name $name
| thread::send $tid {
| tcl::tm::path add [tsv::get shared path]
| package require threads
| threads::process [tsv::get shared name]
| }
| }
| foreach tid $tids { thread::join $tid }
| }
| main
| ------------------------------------------------------------
| # filename: threads-1.tm
| package require thread 3
| namespace eval threads {}
| proc threads::process name {
| puts "processing $name in thread [thread::id]"
| }
| ------------------------------------------------------------
| Using the two files above does work. However it has one problem:
| When I run threads.tcl it correctly does the work but it doesn't
| terminate at the end, just hangs so I have to press Ctrl+C.
| (I'm using Linux if that makes a difference.)
Add some debugging prints in the main thread, then you will see that the 'join's do not finish. This is due to the fact that the threads are
never told to finish. If you add a
thread::send $tid thread::release
just before the thread::join, they will finish (also of course if you thread::release somewhere in the thread code).
Just keep in mind that each thread starts a completely new interpreter
which knows nothing about what was defined in the main thread which
started it. You will need to redefine every proc you need in the
threads, set every variable etc. I would almost certainly put that
startup code in a separate file and source that from every thread,
including the main thread.
R'
* Mark Summerfield <[email protected]>
| Thank you. The append instead of lappend was a typo. I've now changed
| main to this and it terminates & works fine:
| proc main {} {
| set names [list one two three four]
| set tids [list]
| foreach name $names {
| set tid [thread::create -joinable]
| lappend tids $tid
| tsv::set shared path $::APPPATH
| tsv::set shared name $name
| thread::send $tid {
| tcl::tm::path add [tsv::get shared path]
| package require threads
| threads::process [tsv::get shared name]
| }
| }
| foreach tid $tids {
| thread::release $tid
| thread::join $tid
| }
| }
| However, although it works, when I use this technique for the real
| work I'm doing a run takes 0.312s but using the exec-based approach
| takes only 0.026s!
No surprise here, since the exec variant runs the processes in parallel (&), while your thread variant waits for each thread to finish the
thread::send (no -async) (so it really isn't multi-threaded :-)
If you change to [thread::send -async], the code contains at least one possible race condition for the 'name' variable:
foreach name $names {
...
tsv::set shared name $name
...
thread::send $tid { ... threads::process [tsv::get shared name] }
Here that you assume that 'name' is still set to the value that main had
set after starting the thread, but this is not necessarily true when
using -async. If the execution of the thread is delayed for any reason,
the next loop iteration might have already set the next name in the
tsv::set. Threads can be tricky and hard to debug...
R'
Here's the working threaded solution I found: ------------------------------------------------------------
#!/usr/bin/env tclsh9
if {![catch {file readlink [info script]} name]} {
const APPPATH [file dirname $name]
} else {
const APPPATH [file normalize [file dirname [info script]]]
}
package require thread 3
proc main {} {
set names [list one two three four]
set tids [list]
foreach name $names {
set tid [thread::create -joinable]
append tids $tid
tsv::set shared path $::APPPATH
tsv::set shared name $name
thread::send $tid {
tcl::tm::path add [tsv::get shared path]
package require threads
threads::process [tsv::get shared name]
}
}
foreach tid $tids { thread::join $tid }
}
main
------------------------------------------------------------
# filename: threads-1.tm
package require thread 3
namespace eval threads {}
proc threads::process name {
puts "processing $name in thread [thread::id]"
}
You're right that I forgot the -async. That reduces the time to 0.064s,
still more than double the time it took via exec and as you rightly
say much harder to debug. I'll stick with exec ... & for these cases.
Not sure about [package require thread 3] which fails for me,
Sysop: | DaiTengu |
---|---|
Location: | Appleton, WI |
Users: | 1,071 |
Nodes: | 10 (0 / 10) |
Uptime: | 81:24:01 |
Calls: | 13,754 |
Files: | 186,984 |
D/L today: |
7,239 files (2,011M bytes) |
Messages: | 2,425,952 |