Home > Back-end >  How to supply input to a thread which is polling for stdin, form another thread in the same process?
How to supply input to a thread which is polling for stdin, form another thread in the same process?

Time:02-01

Referring to following code example, I want the main thread to supply the number num that the child thread is expecting using scanf. I tried this way to write the wordcount (9) to stdin which is to be read by child thread, but it is not working.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void* child_thread_func(void* terminalflag)
{
        int num=0;
        printf("Trying to read num from stdin\n");
        scanf("%d",&num);
        /*expecting 9 to be printed here*/
        printf("Entered number is %d\n", num);
}


int main () {
        pthread_t tid;
        if (pthread_create(&tid, NULL, &child_thread_func, NULL) != 0) {
                printf("Failed to initialize thread\n");
                exit(1);
        }

        sleep(2);
        char buffer[50];
        FILE *wfp = popen("wc -c", "w");
        if (wfp != NULL) {
            sprintf(buffer, "dummyword");
            int save_stdin = dup(fileno(stdin));
            dup2(fileno(wfp), fileno(stdin));

            fwrite(buffer, sizeof(char), strlen(buffer), wfp);
            dup2(save_stdin, fileno(stdin));
            pclose(wfp);
        }
        pthread_join(tid, NULL);
}  

Can someone suggest a correct way or any other alternative way to do this?
Thanks.

CodePudding user response:

I don't think there is any good way for a process to write text to its own stdin; stdin is meant to be a way for the parent process (or the user, if the parent process is a Terminal window) to send data to your process, not for your process to send data to itself.

However, you could achieve a similar result by having your child thread use select() or similar to read input from both stdin and from the output end of a pipe; then your parent process can send data to the child process by writing to the input end of that same pipe.

Below is a modified version of your program demonstrating the technique. Note that the child thread will print out any text that you type into stdin; and also the main thread will send a line of text to the child thread once every 5 seconds, and the child thread will also print out that text. After the main thread has sent 5 messages to the child thread, the main thread will close its end of the pipe, causing the child thread to exit and then the process can exit cleanly as well.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

static int pipeReadFD = -1;

static int ReadTextFrom(const char * descriptionOfSender, int fd)
{
   char buf[256];
   const int numBytesRead = read(fd, buf, sizeof(buf)-1);  // -1 so we always have room to place NUL terminator byte
   if (numBytesRead > 0)
   {
      buf[numBytesRead] = '\0';  // make sure the string is NUL-terminated
      printf("ReadTextFrom():  Read %i bytes from [%s]: [%s]\n", numBytesRead, descriptionOfSender, buf);
   }
   return numBytesRead;
}

void* init_on_sys_ready(void* terminalflag)
{
   int num=0;
   printf("Child thread:  trying to read text from stdin\n");

   while(1)
   {
      const int stdinFD = fileno(stdin);
      const int maxFD   = (pipeReadFD > stdinFD) ? pipeReadFD : stdinFD;

      fd_set readFDSet;
      FD_ZERO(&readFDSet);
      FD_SET(stdinFD,    &readFDSet);
      FD_SET(pipeReadFD, &readFDSet);

      const int selRet = select(maxFD 1, &readFDSet, NULL, NULL, NULL);
      if (selRet >= 0)
      {
         if ((FD_ISSET(stdinFD,    &readFDSet))&&(ReadTextFrom("stdin", stdinFD)    <= 0)) break;
         if ((FD_ISSET(pipeReadFD, &readFDSet))&&(ReadTextFrom("pipe",  pipeReadFD) <= 0)) break;
      }
      else
      {
         perror("select");
         break;
      }
   }

   printf("Child thread exiting!\n");

   return NULL;
}

int main(int argc, char ** argv)
{
   int pipeFDs[2];
   if (pipe(pipeFDs) < 0)
   {
      perror("pipe");
      return -1;
   }

   pipeReadFD = pipeFDs[0];
   int pipeWriteFD = pipeFDs[1];

   pthread_t tid;
   if (pthread_create(&tid, NULL, &init_on_sys_ready, NULL) != 0) {
      printf("Failed to initialize CLI\n");
      exit(1);
   }

   int count = 0;
   for (int count=0; count < 5; count  )
   {
      char buf[512];
      snprintf(buf, sizeof(buf), "Hello #%i from main thread",   count);

      const size_t slen = strlen(buf);
      if (write(pipeWriteFD, buf, slen) == slen)
      {
         printf("main() sent [%s] to the child thread via the pipe.\n", buf);
      }
      else
      {
         perror("write");
         break;
      }
      sleep(5);
   }

   close(pipeWriteFD);  // this will cause the child thread to exit ASAP
   pthread_join(tid, NULL);

   return 0;
}

CodePudding user response:

popen's man states:

[...] the command's standard output is the same as that of the process that called popen()

So you just need a way to redirect stdout to stdin. Which is exactly what pipe is for. It links an output fd with an input fd. As pipe creates new fds, we need to use dup2 to replace stdin and stdout, as you've already did in your example code. Threads share the same memory, so you don't have to worry about any child/parent differences in fds.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

void* child_thread_func(void* terminalflag)
{
    int num=0;
    printf("Trying to read num from stdin\n");
    scanf("%d",&num);
    /*expecting 9 to be printed here*/
    printf("Entered number is %d\n", num);
}


int main () {
    setbuf(stdin, NULL);

    pthread_t tid;
    if (pthread_create(&tid, NULL, &child_thread_func, NULL) != 0) {
        printf("Failed to initialize thread\n");
        exit(1);
    }

    int save_stdin = dup(STDIN_FILENO);
    int save_stdout = dup(STDOUT_FILENO);
    int tube[2];
    pipe(tube);
    dup2(tube[0], STDIN_FILENO);
    dup2(tube[1], STDOUT_FILENO);
        
    char buffer[50] = {0};
    FILE *wfp = popen("wc -c", "w");
    if (wfp != NULL) {
        sprintf(buffer, "dummyword");
    
        fwrite(buffer, sizeof(char), strlen(buffer), wfp);
        pclose(wfp);
    }
    dup2(save_stdin, STDIN_FILENO);
    dup2(save_stdout, STDOUT_FILENO);
    pthread_join(tid, NULL);
}
  • Related