confused about thread::wait and thread::send

60 views
Skip to first unread message

ted brown

unread,
Aug 30, 2021, 10:22:51 PMAug 30
to
I'm a bit confused about just what thread::wait does. The manual says it
should be the very last command in the script. I'm assuming that also
applies to using it in a loop.

I was thinking about some code in a thread that would accept some data
to work on, and use thread::wait to wait for something to be sent. But
it seems I can't do what I would normally think of as a standard work
loop like so:

set t1 [thread::create {
while 1 {
thread::wait
.. wake up when something arrives ...
process arrival
send back result
}
} ]

Also, on the thread::send there's the ?varname? last arg. Can it
retrieve the result more than once if more than one thread::send is
done? What if a send occurs while an earlier one is still processing?
Does it queue them up?

Since code is sent, rather than data, will they all trigger together
before it even wakes up?

Or do I have this completely wrong?

What's the best way to do this (best = simplest in this case)?

Rich

unread,
Aug 30, 2021, 11:23:20 PMAug 30
to
ted brown <tedbr...@gmail.com> wrote:
> I'm a bit confused about just what thread::wait does. The manual says it
> should be the very last command in the script. I'm assuming that also
> applies to using it in a loop.

It enters the event loop. Useful if your script is in a tclsh that
does not normally have an event loop running. Not technicaly needed if
your script runs in a wish (or by package require Tk) where you always
have an event loop running.

> I was thinking about some code in a thread that would accept some data
> to work on, and use thread::wait to wait for something to be sent. But
> it seems I can't do what I would normally think of as a standard work
> loop like so:
>
> set t1 [thread::create {
> while 1 {
> thread::wait
> .. wake up when something arrives ...
> process arrival
> send back result
> }
> } ]

That looks a lot like yield for coroutines, thread send messages don't
work that way.

> Also, on the thread::send there's the ?varname? last arg. Can it
> retrieve the result more than once if more than one thread::send is
> done? What if a send occurs while an earlier one is still processing?
> Does it queue them up?

I believe they queue up and you can get them back in the order they
were created.

> Since code is sent, rather than data, will they all trigger together
> before it even wakes up?

Each 'send' is a separate "event".

> Or do I have this completely wrong?

A bit mixed up, here and there, but not completely wrong.

> What's the best way to do this (best = simplest in this case)?

A pattern I've used before is to treat thread::sends as 'messages' (I
often toss them over using -async as well, in which case they become
'fire and forget'). And then in the receiver, have it 'thread::send'
it's result on to where it should go. I.e., something like this psudeo
code:

parent thread code (assume Tk is running):

set t1 [thread::create {
proc do-something {a b c} {
lappend result [do-something1 $a] ;# assume do-something
lappend result [do-something2 $b]
lappend result [do-something3 $c]
# assume this thread was 'informed' somehow of the parent thread ID
# (tsv is useful for this)
thread::send -async $parent [list the-answer-is $result]
}
}

proc the-answer-is {result} {
display-the-answer $result ;# assume 'display-the-answer' does
# whatever necessary to show/print/output/etc. the answer
}

button .b -text "Do something" \
-command [list thread::send -async $t1 do-something $z $y $x]
# assume z y x are -textvars connected to other widgets

Pressing the button triggers an event that runs the -command, which
does an async thread::send into the thread, calling the proc inside the
thread. Multiple 'sends' to the thread queue up on the thread's event
loop and are processed in order.

The thread, when done, then does its own async thread send back to the
parent, to a different proc, that handles the "display" of the data in
some way.

This is sort of a RPC/message hybrid concept. The individual async
thread send's are passing messages around. The messages execute
"remote procedures".

ted brown

unread,
Aug 31, 2021, 1:51:50 AMAug 31
to
On 8/30/2021 8:23 PM, Rich wrote:

Hmmm, interesting. I think I see, just a few follow-up questions.

>
> parent thread code (assume Tk is running):
>
> set t1 [thread::create {
> proc do-something {a b c} {
> lappend result [do-something1 $a] ;# assume do-something
> lappend result [do-something2 $b]
> lappend result [do-something3 $c]
> # assume this thread was 'informed' somehow of the parent thread ID
> # (tsv is useful for this)
> thread::send -async $parent [list the-answer-is $result]
> }
> }

So, this seems similar to wish/tclsh running a script where if wish,
reaching the end of the script file it enters the event loop, while if
started by tclsh, it exits instead - or one uses an explicit vwait.

So, analogously reaching the end of the thread script also either enters
the event loop (if started with wish) or exits (if started by tclsh).

Is that the right way to think about it?

And so the thread::wait does what the vwait does in the regular script
when you startup with tclsh.

Now with regular events, if the code triggered by an event does a vwait
itself, then the next queued event will trigger, which in our case here,
with just the 1 proc, would re-enter do-something interrupting the
earlier call to do-something. Is that correct?

So to keep do-something from being interrupted, one must not do
anything, like an update, or vwait. Or else, I guess to keep them
sequential, one would have to use some thread locking primitives.


>
> proc the-answer-is {result} {
> display-the-answer $result ;# assume 'display-the-answer' does
> # whatever necessary to show/print/output/etc. the answer
> }


So, this proc is similar to one that might be called via the script arg
in a bind call except the "bounded" event is the thread::send back to
the parent.

>
> button .b -text "Do something" \
> -command [list thread::send -async $t1 do-something $z $y $x]
> # assume z y x are -textvars connected to other widgets
>

Is z, y, and x, being connected to widgets just as an example, or is
there something special about those 3 variables?

Also, I thought the script had to be in one arg, should that be

-command [list thread::send -async $t1 [list do-something $z $y $x]]

> This is sort of a RPC/message hybrid concept. The individual async
> thread send's are passing messages around. The messages execute
> "remote procedures".
>

Thanks so much, I think I now have a way to think about it that is not
too different from what I'm used to - assuming my above thinking is
correct :)



ted brown

unread,
Aug 31, 2021, 1:59:04 AMAug 31
to
On 8/30/2021 10:17 PM, EL wrote:
> ted brown <tedbr...@gmail.com> wrote:
>> I'm a bit confused about just what thread::wait does. The manual says it
>> should be the very last command in the script. I'm assuming that also
>> applies to using it in a loop.
>
> No, you don’t need a loop. Call thread::wait directly and you‘re set. And
> if you create the thread without a script, with [thread::create], then the
> thread waits automatically.
>
> But it seems that you want to do something that is not achievable with
> thread::wait, namely sending signals between threads and let one thread
> wait for another to finish some work etc. This is best done with
> thread::condition variables. It’s also where the loop comes into the game.
> Condition variables enable you to wait in a thread for something that
> happens in another thread, you can use them to signal there and back
> from/to the threads in your program.
>
>

Thanks, I'll look into the condition variables once I fully understand
what Rich wrote.

Two things though, puts is my usual debugger, and on windows I can't get
puts to go to a console (from inside a thread::create script) nor can I
see any errors pop up. It seems the thread just quits silently if I mess up.

How do you debug your threads?

Rich

unread,
Aug 31, 2021, 7:45:13 AMAug 31
to
ted brown <tedbr...@gmail.com> wrote:
> On 8/30/2021 8:23 PM, Rich wrote:
>
> Hmmm, interesting. I think I see, just a few follow-up questions.
>
>>
>> parent thread code (assume Tk is running):
>>
>> set t1 [thread::create {
>> proc do-something {a b c} {
>> lappend result [do-something1 $a] ;# assume do-something
>> lappend result [do-something2 $b]
>> lappend result [do-something3 $c]
>> # assume this thread was 'informed' somehow of the parent thread ID
>> # (tsv is useful for this)
>> thread::send -async $parent [list the-answer-is $result]
>> }
>> }
>
> So, this seems similar to wish/tclsh running a script where if wish,
> reaching the end of the script file it enters the event loop, while if
> started by tclsh, it exits instead - or one uses an explicit vwait.

Yes.

> So, analogously reaching the end of the thread script also either enters
> the event loop (if started with wish) or exits (if started by tclsh).

This has been my experience so far.

> Is that the right way to think about it?

It seems reasonable. Others may wish to add corrections.

> And so the thread::wait does what the vwait does in the regular script
> when you startup with tclsh.
>
> Now with regular events, if the code triggered by an event does a vwait
> itself, then the next queued event will trigger, which in our case here,
> with just the 1 proc, would re-enter do-something interrupting the
> earlier call to do-something. Is that correct?

This is very likely, so you would need to use care (as always) when
having a proc in a thread perform an additional vwait.

> So to keep do-something from being interrupted, one must not do
> anything, like an update, or vwait. Or else, I guess to keep them
> sequential, one would have to use some thread locking primitives.

Either would be possible, and the threads package does provide the
usual set of sync. primitives. But with the exception of needing to
handle code that does a 'vwait' you don't otherwise have to worry about
these items.

>> proc the-answer-is {result} {
>> display-the-answer $result ;# assume 'display-the-answer' does
>> # whatever necessary to show/print/output/etc. the answer
>> }
>
>
> So, this proc is similar to one that might be called via the script arg
> in a bind call except the "bounded" event is the thread::send back to
> the parent.

In a way, yes. The flow becomes event driven. Button press event
results in "message send" event to the other thread. Later, when the
other thread finishes, it creates another "message send" event back to
the parent (or to some other 'next' thread).

>> button .b -text "Do something" \
>> -command [list thread::send -async $t1 do-something $z $y $x]
>> # assume z y x are -textvars connected to other widgets
>>
>
> Is z, y, and x, being connected to widgets just as an example, or is
> there something special about those 3 variables?

Just example names to fill in the psudeocode. I.e., think of them as
being attached to entry widgets via the -textvariable option.

> Also, I thought the script had to be in one arg, should that be
>
> -command [list thread::send -async $t1 [list do-something $z $y $x]]

Yes, you are correct. I did omit another "listification" there. Well,
I did call it "psudeocode". :)

>> This is sort of a RPC/message hybrid concept. The individual async
>> thread send's are passing messages around. The messages execute
>> "remote procedures".
>>
>
> Thanks so much, I think I now have a way to think about it that is
> not too different from what I'm used to - assuming my above thinking
> is correct :)

You appear (to the extent I can tell via Usenet) to be on a reasonable
path. And, as always, one of the best ways to learn is to give it a
try.

Rich

unread,
Aug 31, 2021, 7:52:32 AMAug 31
to
ted brown <tedbr...@gmail.com> wrote:
> On 8/30/2021 10:17 PM, EL wrote:
>> ted brown <tedbr...@gmail.com> wrote:
>>> I'm a bit confused about just what thread::wait does. The manual says it
>>> should be the very last command in the script. I'm assuming that also
>>> applies to using it in a loop.
>>
>> No, you don?t need a loop. Call thread::wait directly and you?re set. And
>> if you create the thread without a script, with [thread::create], then the
>> thread waits automatically.
>>
>> But it seems that you want to do something that is not achievable with
>> thread::wait, namely sending signals between threads and let one thread
>> wait for another to finish some work etc. This is best done with
>> thread::condition variables. It?s also where the loop comes into the game.
>> Condition variables enable you to wait in a thread for something that
>> happens in another thread, you can use them to signal there and back
>> from/to the threads in your program.
>>
>>
>
> Thanks, I'll look into the condition variables once I fully understand
> what Rich wrote.
>
> Two things though, puts is my usual debugger, and on windows I can't get
> puts to go to a console (from inside a thread::create script) nor can I
> see any errors pop up. It seems the thread just quits silently if I mess up.
>
> How do you debug your threads?

By not using Windows as the OS.... :) (yes, really).

However, for those instances where I am forced to build some Tcl on
that inferior OS, what I do is one of:

1) create a "log" (or other suitable name) proc in the parent
(original) thread
2) create "wrapper" procs in the child threads that abstracts away a
'thread::send -async $parent [list log ....]' that is used instead
of 'puts' in the child threads.

or

1) load the tcllib 'log' package in the parent
2) create wrapper procs in the child threads that abstract away a
'thread::send -async $parent [??? .....]' for the various ???
procedures of the log package I want to use from the children
threads

And, as the master parent thread 'can' open a console on Windows, I can
see the 'puts' messages there. Or if I've used Tcllib's log, it allows
redirecting the log output to a file.

ted brown

unread,
Aug 31, 2021, 3:43:45 PMAug 31
to
> When you use tclsh on windows from a cmd or powershell rather than wish,
> the puts goes on the cmd/ps. That’s how I do it…

Hmmm, interesting. I use a tclkit with gui and twapi that I get from
Ashok. I don't even install tcl/tk on windows any longer.

I tried several ways to rename and/or proc replace [puts] inside a tcl
thread with no success. I have a collection of tools that use puts to
list globals, widget trees, etc. But with no console, I don't yet have
a way to run them.

What I've just now tossed together is a putz command that creates a
toplevel and a text widget. I'm pretty happy with how well Tk can run
inside a thread, which was mentioned in that posting comparing processes
to threads.

>
> But admittedly I don’t use the Threads package a lot, I usually work with
> threads in C/C++ in Tcl extensions. Just stumbled over condition variables
> in the Threads package documentation recently, and found out that it’s the
> same concept as with C++ std::condition_variable. Not really surprising,
> though, but I was surprised that they also exist at the Tcl level ;)
>
>

I've mostly just used [exec] and localhost sockets for this in the past.
Now I want to try my hand at threads.

I'm hoping I can use condition variables to build my own layer on top of
threads to mimic a lightweight process or "task". At the higher level,
it will be sending argc/argv like data, implemented with thread::send
scripts.

In order for my "tasks" to safely run sequentially while being free to
use update or vwait, I figure I need a work queue that I can atomically
access on both ends, plus a way to wait when it's empty.

Either mutexes and/or condition variables with tsv might be just the ticket.

Years ago when I programmed the VAX computer, it had a set of atomic
queue instructions, which I used to implement interrupt safe lists in C
code. It should be fun to figure this out inside tcl/tk with threads.

Thanks for the tips.

ted brown

unread,
Aug 31, 2021, 4:07:24 PMAug 31
to
On 8/31/2021 4:52 AM, Rich wrote:

>>
>> How do you debug your threads?
>
> By not using Windows as the OS.... :) (yes, really).

I too have a love hate relationship with windows. Mostly I use it
because I have 25 years worth of tools I've collected that still run. I
even still use Office 97's excel for some rather old spreadsheets.

But it goes back even further, since Dave Cutler's prior 2 OS's on the
pdp-11 and vax were the precursor to windows NT. I can still see some of
Dave's style in how Windows internals work. But even then it was the
third party hardware/software that was the benefit. Pdp-11's were
everywhere back then.

I also have some rather nice windows development tools by a guy named
Jan Goyvaerts who markets editpad pro, powergrep, and regexbuddy. I have
a few linux systems in VMs with a rasp pi also, and I can easily run
these tools on files there using samba, and also edit files directly on
my android phones and tablets over sftp, also built into the tools.

And on windows, with tcl/tk, there's Ashok's twapi, which I use very
often for scripting tedious gui actions. I can even synchronize some
parts of my script by waiting until some gui window's title changes, so
I know the program is ready for some mouse clicks sent to it.

But I hate win 10's forced updates so much, I block them with my
router's access control page.

>
> And, as the master parent thread 'can' open a console on Windows, I can
> see the 'puts' messages there. Or if I've used Tcllib's log, it allows
> redirecting the log output to a file.
>

Hmmm, maybe I'll just need to build something that can run in the
windows console, send to the thread and get some output to a text widget
I've been using inside the thread for some visibility.

Thanks so much, I am feeling much more confident using threads that just
a few days ago.

js

unread,
Aug 31, 2021, 4:27:04 PMAug 31
to
On 8/31/21 3:43 PM, ted brown wrote:
>
> I tried several ways to rename and/or proc replace [puts] inside a tcl
> thread with no success. I have a collection of tools that use puts to
> list globals, widget trees, etc.  But with no console, I don't yet have
> a way to run them.
>
> What I've just now tossed together is a putz command that creates a
> toplevel and a text widget. I'm pretty happy with how well Tk can run
> inside a thread, which was mentioned in that posting comparing processes
> to threads.
>

I was fortunate to learn about threads from a mentor several years ago
and he had a nice trick for this that I have been using almost verbatim
since (this is from memory so hopefully no embarrassing mistakes):

In the main app:
proc got_puts_from_sub {msg} {
puts "$msg"
flush stdout
}

In the sub-thread:
proc puts_sub {msg} {
thread::send -async MAIN_INTERP_ID [list got_puts_from_sub $msg]
}

Then, in the sub-thread, whenever I need to use puts, I simply use
puts_sub. You can replace MAIN_INTER_ID with the actual parent interp's
thread id in several ways.

ted brown

unread,
Aug 31, 2021, 4:52:12 PMAug 31
to
Interesting, I have a piece of code that was from heinrichmartin that
will use info body/args/default to retrieve the full text of a proc that
can then be changed (using regsub) and then eval'd to change a puts to
something else, maybe a puts_sub, for code I then "import" into the
thread. I Love tcl's dynamic capabilities.

Then I just need a way to issue commands. Probably I'll just use a text
entry widget inside the thread. Then I'll have all my 2 letter l?
commands I use for listing globals, arrays, dicts, etc.

Thanks.

ted brown

unread,
Sep 1, 2021, 12:04:32 AMSep 1
to
On 8/31/2021 12:43 PM, ted brown wrote:
> On 8/31/2021 1:37 AM, EL wrote:
>> ted brown <tedbr...@gmail.com> wrote:
> I'm hoping I can use condition variables to build my own layer on top of
> threads


I found a perfect example in Ashok's book "The Tcl programming
language". Section 22.11.2 on condition variables has as its example,
just what I need, with a condition variable, mutex, and a shared
variable list. Made to order!

Thanks all.

EL

unread,
Sep 1, 2021, 12:02:04 PMSep 1
to
Ashok has also created a promise module and wrote some articles about it:

https://tcl-promise.magicsplat.com/
https://www.magicsplat.com/blog/tags/promises/

If your objective is to just run some code in parallel from the main
thread and wait for the results of these computations, then you might
find this interesting as well.


--
EL

ted brown

unread,
Sep 1, 2021, 5:44:29 PMSep 1
to
Thanks for the suggestion. I have already looked into promises. In a
sense, I'm attempting to create my own threads abstraction layer but
with a different goal. Mine is maximal simplicity.

My objective is mostly to learn to use tcl threads. My usual approach is
to invent a small project using the tool I wish to learn. The project
need not be useful.

So, I've set myself to inventing what I'll call a task. It is a way to
call a proc in its own thread, and either wait for it, or catch up to it
later using a [bind] like mechanism. It will only support some common
uses for threads.

I don't know if I will succeed; if I do, I'll create a wiki page. If
not, well I should have learned something about tcl threads.



Here's my set of primitives so far:

#################################
#
# From a parent thread - "name" for building tsv shared variables
#
# set taskid [task name prefixlist script]
# set result [tcall $taskid ?-async? ?arg..?]
# tbind $taskid [list callback_prefix ... ]
#
# inside a task
#
# twait args-var ;#waits for work to do and get args
# treturn ?-exit? result ;# if no -exit, should be in a loop
#
#################################

What might this be used for? Say you want to solve the common problem of
a heavy compute proc, but want the gui to remain responsive, for example:

proc heavy {args} {
...
return $result
}


Something like this should do the trick to thread-ify heavy:

#--------------
set heavy_id [task heavy_task {heavy ...} {

twait argv ; lassign $argv arg1 arg2 ... ;# wait till called, get args
set result [heavy $arg1 $arg2 ....] ;# heavy was imported
treturn -exit $result

}
#--------------

and the main (or parent) thread would do this:

set result [tcall $heavy_id arg1 .....]

This will create a thread and retrieve [heavy] using something similar
to Ashok's "reconstruct" proc. It then calls it synchronously.

Anyway, that's as far as I've got, and have only started coding the
[task] call, but have the reconstruct working nicely.

The queue and condition variable example would be used behind the scenes
so that multiple calls would queue up and run sequentially. Adding a
loop around the 3 lines and removing the -exit turns it into a
client/server thread. All the complex queue stuff remains hidden.

Only building it and trying it out will determine if it's of any use.
The journey is more important than the destination however.


Reply all
Reply to author
Forward
0 new messages