2

I am trying to read some data from stdin in a separate thread from main thread. Main thread should be able to communicate to this waiting thread by writing to stdin, but when I run the test code (included below) nothing happens except that the message ('do_some_work' in my test code) is printed on the terminal directly instead of being output from the waiting thread.

I have tried a couple of solutions listed on SO but with no success. My code mimics one of the solutions from following SO question, and it works perfectly fine by itself but when coupled with my read_stdin_thread it does not.

Is it possible to write data into own stdin in Linux

#include <unistd.h>
#include <string>
#include <iostream>
#include <sstream>
#include <thread>

bool terminate_read = true;

void readStdin() {

    static const int INPUT_BUF_SIZE = 1024;
    char buf[INPUT_BUF_SIZE];

    while (terminate_read) {
        fd_set readfds;
        struct timeval tv;
        int data;

        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        tv.tv_sec=2;
        tv.tv_usec=0;
        int ret = select(16, &readfds, 0, 0, &tv);
        if (ret == 0) {
            continue;
        } else if (ret == -1) {
            perror("select");
            continue;
        }
        data=FD_ISSET(STDIN_FILENO, &readfds);
        if (data>0) {
            int bytes = read(STDIN_FILENO,buf,INPUT_BUF_SIZE);
            if (bytes == -1) {
                perror("input poll: read");
                continue;
            }
            if (bytes) {
                std::cout << "Execute: " << buf << std::endl;
                if (strncmp(buf, "quit", 4)==0) {
                    std::cout << "quitting reading from stdin." << std::endl;
                    break;
                }
                else {
                    continue;
                }
            }
        }
    }
}

int main() {
    std::thread threadReadStdin([] () {
        readStdin();
    });

    usleep(1000000);
    std::stringstream msg;
    msg << "do_some_work" << std::endl;
    auto s = msg.str();
    write(STDIN_FILENO, s.c_str(), s.size());
    usleep(1000000);

    terminate_read = false;
    threadReadStdin.join();

    return 0;
}

A code snippet illustrating how to write to stdin that in turn is read by threadReadStdin would be extremely helpful.

Thanks much in advance!

Edit:

One thing I forgot to mention here that code within readStdin() is a third party code and any kind of communication that takes place has to be on its terms.

Also, I am pretty easily able to redirect std::cin and std::cout to either fstream or stringstream. Problem is that when I write to redirected cin buffer nothing really appears on the reading thread.

Edit2:

This is a single process application and spawning is not an option.

Knight Forked
  • 1,529
  • 12
  • 14
  • 1
    `stdin` and `stdout` are not linked together such that you can write to `stdout` and have it appear in `stdin` of the same process. Also, on your `select()`, you need to use `STDIN_FILENO+1` instead of hard-coding `16`. In any case, there are better ways to send data between threads that don't involve hijacking console input, such as using a [pipe](http://man7.org/linux/man-pages/man2/pipe.2.html) instead. – Remy Lebeau Apr 09 '19 at 23:05
  • @RemyLebeau Thanks for prompt answer. No, I am not looking to write to stdout and have it appear in stdin. I am trying to write to stdin buffer that is subsequently taken as an input by the thread as if it were coming from stdin. Not sure if that is even possible. Basically I would like to have stdin redirected. – Knight Forked Apr 10 '19 at 06:31

2 Answers2

2

If you want to use a pipe to communicate between different threads in the same program, you shouldn't try using stdin or stdout. Instead, just use the pipe function to create your own pipe. I'll walk you through doing this step-by-step!

Opening the channel

Let's create a helper function to open the channel using pipe. This function takes two ints by reference - the read end and the write end. It tries opening the pipe, and if it can't, it prints an error.

#include <unistd.h>
#include <cstdio>
#include <thread>
#include <string>

void open_channel(int& read_fd, int& write_fd) {
    int vals[2];
    int errc = pipe(vals); 
    if(errc) {
        fputs("Bad pipe", stderr); 
        read_fd = -1;
        write_fd = -1; 
    } else {
        read_fd = vals[0];
        write_fd = vals[1]; 
    }
}

Writing a message

Next, we define a function to write the message. This function is given as a lambda, so that we can pass it directly to the thread.

auto write_message = [](int write_fd, std::string message) {
    ssize_t amnt_written = write(write_fd, message.data(), message.size());
    if(amnt_written != message.size()) {
        fputs("Bad write", stderr); 
    }
    close(write_fd); 
}; 

Reading a message

We should also make a function to read the message. Reading the message will be done on a different thread. This lambda reads the message 1000 bytes at a type, and prints it to standard out.

auto read_message = [](int read_fd) {
    constexpr int buffer_size = 1000; 
    char buffer[buffer_size + 1]; 
    ssize_t amnt_read; 
    do {
        amnt_read = read(read_fd, &buffer[0], buffer_size);
        buffer[amnt_read] = 0; 
        fwrite(buffer, 1, amnt_read, stdout); 
    } while(amnt_read > 0); 
};

Main method

Finally, we can write the main method. It opens the channel, writes the message on one thread, and reads it on the other thread.

int main() {
    int read_fd;
    int write_fd;
    open_channel(read_fd, write_fd); 

    std::thread write_thread(
        write_message, write_fd, "Hello, world!"); 
    std::thread read_thread(
        read_message, read_fd); 
    write_thread.join(); 
    read_thread.join(); 
}
Alecto Irene Perez
  • 10,321
  • 23
  • 46
  • thanks for the alternative solution but I cannot really do away with reading from stdin (i.e. the readStdin() function) since it's a part of third party code that I have to interact with. – Knight Forked Apr 10 '19 at 06:26
  • 2
    You can `fork()` and then `dup2` the stdin and substitute it for child process with the pipe. – KamilCuk Apr 10 '19 at 06:49
  • @KamilCuk this is a single process application and spawning is not a option. I woudn't have to ask the question if that were the case :)...thanks for your suggestion though! – Knight Forked Apr 10 '19 at 07:22
1

It seems like I have stumbled upon the answer with the help of very constructive responses from @Jorge Perez, @Remy Lebeau and @Kamil Cuk. This solution is built upon @Jorge Perez's extremely helpful code. For brevity's sake I am not including the whole code but part comes from the code I posted and a large part comes from @Jorge Perez's code.

What I have done is taken his approach using pipes and replacing STDIN_FILENO by the pipe read fd using dup. Following link was really helpful:

https://en.wikipedia.org/wiki/Dup_(system_call)

I would really appreciate your input on whether this is a hack or a good enough approach/solution given the constraints I have in production environment code.

int main() {
    int read_fd;
    int write_fd;

    open_channel(read_fd, write_fd); 

    close(STDIN_FILENO);
    if(dup(read_fd) == -1)
        return -1;

    std::thread write_thread(write_message, write_fd, "Whatsup?"); 
    std::thread threadReadStdin([] () {
        readStdin();
    });

    write_thread.join(); 
    threadReadStdin.join();

    return 0;
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Knight Forked
  • 1,529
  • 12
  • 14