Search code examples
c++linuxmultithreadingkeypress

C++ handle keypress in a multithread program in Linux


I'am writing a program which capturing videos from 4 cameras simultaneously, so I have 4 threads to control each camera. In each thread I want it to continue capturing until I hit a key and that key corresponds to 'q' or something.

For keypress handle I search the Internet and found method like this:

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

int kbhit(int key) {
    int ch;
    int old_file_status;
    struct termios old_term_attr;
    struct termios new_term_attr;

    tcgetattr(STDIN_FILENO, &old_term_attr);
    new_term_attr = old_term_attr;
    new_term_attr.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &new_term_attr);

    old_file_status = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, old_file_status | O_NONBLOCK);

    ch = getchar();

    tcsetattr(STDIN_FILENO, TCSANOW, &old_term_attr);
    fcntl(STDIN_FILENO, F_SETFL, old_file_status);

    if(ch == c)
        return 1;
    return 0;
}

And in my VideoCapture class I have code like this (not complete):

static void *capureVideo(void *para) {
    // Some code...
    while(!(kbhit('q') {
        // Read frame...
    }
}

void creatThread() {
    if (pthread_create(&threadID, NULL, capureVideo, this) != 0) {
        perror("thread create faild");
        exit(EXIT_FAILURE);
    }    
}

When the program runs, once I hit the key 'q' 4 times the program did quits and control is given back to the shell. But in some certain circumstances (I don't exactly know, it doesn't happen every time) it lead to a problem, that is when I then go and type commands into the shell, the characters I type don't show up. When I press enter the commands are submitted.

I search this problem and found this: https://askubuntu.com/a/172747, which indicates that my terminal attributes were not reset properly. But in the keypress handle code I noticed that this two lines of code

tcsetattr(STDIN_FILENO, TCSANOW, &old_term_attr);
fcntl(STDIN_FILENO, F_SETFL, old_file_status);

did reset the terminal attributes. So I wonder if it's relevant to the multithread. I'm new to mutlithread programming and couldn't solve it myself, so could someone help me? Any suggestions are highly appreciated.


Solution

  • You have multiple threads trying to change parameters of the (static) terminal at the same time:

    tcgetattr(STDIN_FILENO, &old_term_attr);
    new_term_attr = old_term_attr;
    new_term_attr.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &new_term_attr);
    

    Without this being locked, your terminal attributes are completely random. The fixes are to either protect this with a mutex, or if you spawn another thread to read the keyboard and set a flag for when 'q' has been pressed; which your other threads can read, you can do something like

    (pardon the psudo code)
    bool shouldRun = true
    
    
    void captureThreadMain {
        while (shouldRun) {
            captureFrame();
        }
    }
    
    void keyboardPressMain {
        while (getKey('q'));
        shouldRun = false;
    }
    

    This will mean that you only need to press 'q' once to stop all your frame collecting threads.