Thread overview
Thread to watch keyboard during main's infinite loop
May 07, 2020
Daren Scot Wilson
May 07, 2020
ag0aep6g
May 07, 2020
Daren Scot Wilson
May 07, 2020
Adam D. Ruppe
May 07, 2020
I'm writing a simple command line tool to send data by UDP once per second forever, to test some software on another machine.  Not actually forever, of course, but until ^C or I hit 'Q'. I want to tap keys to make other things happen, like change the data or rate of sending.

Not sure of the best way to do this. Thought I'd try a thread whose job is just to loop, calling readln() or getch() or something similar, and setting a global variables according to what key was tapped.  The loop in the main thread can then break, or do some other action, according to the value of that variable.

Code I have written is below, stripped to just the stuff relevant to key watching. It doesn't work.  When it runs, it prints "Repetitive work" over and over, but never see anything appear in "cmd [ ]".  When I tap 'A' I expect to see "cmd [A]" and a line of several 'A'.  This does not happen.  But the writeln in cmdwatcher() does show whatever I typed just fine.

How to fix this, or redesign the whole thing to work?

Note that I'm coming from science and fine art. My know-how in computer science is uneven, and I probably have gaps in my knowledge about threads.


import std.stdio;
import core.stdc.stdio;  // for getchar().  There's nothing similar in D std libs?
import std.concurrency;
import core.thread; // just for sleep()

bool running=true;
char command = '?';

void cmdwatcher()
{
    writeln("Key Watcher");
    while (running)  {
        char c = cast(char)getchar();
        if (c>=' ')  {
            command = c;
            writefln("     key %c  %04X", c, c);
        }
    }
}

void main()
{
    writeln("Start main");
    spawn(&cmdwatcher);

    while (running) {
        writeln("Repetitive work");
        Thread.sleep( dur!("msecs")( 900 ) );

        char cmd = command;  // local copy can't change during rest of this loop
        command = ' ';
        writefln("cmd [%c]  running %d", cmd, running);

        switch (cmd)
        {
            case 'A':
                    writeln("A A A A A");
                    break;

            case 'Q':
                    writeln("Quitting");
                    running=false;
                    break;
            default:
                break;
        }
    }
}





May 07, 2020
On 07.05.20 02:13, Daren Scot Wilson wrote:
> import std.stdio;
> import core.stdc.stdio;  // for getchar().  There's nothing similar in D std libs?
> import std.concurrency;
> import core.thread; // just for sleep()

Instead of the comment you can write:

    import core.thread: sleep;

> bool running=true;
> char command = '?';

These variables are thread-local by default. That means independent `running` and `command` variables are created for every thread. If you make changes in one thread, they won't be visible in another thread.

Use `shared` so that all threads use the same variables:

    shared bool running=true;
    shared char command = '?';

> void cmdwatcher()
> {
>      writeln("Key Watcher");
>      while (running)  {
>          char c = cast(char)getchar();
>          if (c>=' ')  {
>              command = c;
>              writefln("     key %c  %04X", c, c);
>          }
>      }
> }
> 
> void main()
> {
>      writeln("Start main");
>      spawn(&cmdwatcher);
> 
>      while (running) {
>          writeln("Repetitive work");
>          Thread.sleep( dur!("msecs")( 900 ) );
> 
>          char cmd = command;  // local copy can't change during rest of this loop

For values that don't change, we've got `immutable`:

    immutable char cmd = command;

>          command = ' ';

Note that even when using `shared` you still have to think hard to avoid race conditions.

This sequence of events is entirely possible:

    1) main: cmd = command
    2) cmdwatcher: command = c
    3) main: command = ' '

It won't happen often, but if it does, your input has no effect.
May 07, 2020
On Thursday, 7 May 2020 at 01:02:57 UTC, ag0aep6g wrote:


Thank you, this is 110% helpful.

Actually, I'd like to return the excess 10%.  My dmd compiler does not like:

   import core.thread: sleep;

so I put the code back the way I had, just to get on with work.

> Use `shared` so that all threads use the same variables:
>
>     shared bool running=true;
>     shared char command = '?';
>

"shared" did the job.  I had read about "thread local" and "shared" in D before, but did not comprehend. Now I do :)


> This sequence of events is entirely possible:
>
>     1) main: cmd = command
>     2) cmdwatcher: command = c
>     3) main: command = ' '
>
> It won't happen often, but if it does, your input has no effect.

For this tool, lost key hits are not a problem.  At least, that's what I say for now. I may be back next week for help with that.  For now, the trousered ape running the software will just have to tap the key again. (Or the key + Enter.)
May 07, 2020
On Thursday, 7 May 2020 at 01:33:12 UTC, Daren Scot Wilson wrote:
>    import core.thread: sleep;

It sould be

import core.thread : Thread;

Thread.sleep(1.secs); // or whatever

sleep is a static method on the Thread class.