3

Question: To create a program which takes user input but times out after some seconds (say it's 2 seconds for now).

Approach: I created two threads, one to wait for user input (inputThread with tid[0]) and other to sleep for 2 seconds (sleepThread with tid[1]). I cancel a thread from another thread's routine, as follows:

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

pthread_t tid[2];

void* inputThread()
{   
    int n;
    printf("Enter number:");

    // wait for input; if input is given then go ahead and cancel the sleeper thread.
    scanf("%d",&n); 

    // cancel the sleeper thread
    pthread_cancel(tid[1]); 

    printf("Got value:%d\n",n);
}

void* sleepThread()
{
    // sleep for 2 seconds and cancel the input thread.
    sleep(2); 

    // cancel the input thread
    pthread_cancel(tid[0]); 

    printf("\nNo value entered!\n");
}

int main(int argc, char const *argv[])
{
    int r1,r2,r3,r4;

    // input taking thread 
    r1 = pthread_create(&tid[0],NULL,inputThread,NULL); 

    // sleeping thread
    r2 = pthread_create(&tid[1],NULL,sleepThread,NULL);

    r3 = pthread_join(tid[0],NULL);
    r4 = pthread_join(tid[1],NULL);

    return 0;
}

As of now, the program works as expected.

But my friend says that it's not guaranteed to work since it depends on how threads are scheduled. He tried explaining the same to me but I couldn't understand. He also said that pthread_cancel is only a request to cancel the threads and it may not succeed.

So can someone please point out the potential mistake and best practice to avoid the same. Any changes to be made to guarantee the program's working is also appreciated.

Rahul Bharadwaj
  • 2,555
  • 2
  • 18
  • 29

3 Answers3

3

Your friend is right about the part that pthread_cancel is a request to the cancel the thread and might not succeed always.

For the part about scheduling you need to provide what arguments he had.

For a solution to your problem, you can abandon using threads all together (unless that is a requirement for your assignment) and use select with read.

This will make the solution not-portable (only conforming to POSIX), but pthreads is also anyway not portable.

select allows you to wait on a file descriptor (in this case 0) for data to be available for a read. It also allows you to set a timeout.

fd_set set;
FD_ZERO(&set);
FD_SET(0, &set);

struct timeval timeout;
timeout.tv_sec = 2;
timeout.tv_usec = 0;

int ret = select(1, &set, NULL, NULL, &timeout);

if (ret == 1) {
    char buffer[20];
    int n;
    read(0, buffer, 20);
    sscanf(buffer, "%d", &n);
    printf("Got value:%d\n",n);
} else {
    printf("Time out\n");
}
Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • Thank you for the solution. It's not a requirement to use threads, but we were using threads to just learn the thread library. If one were to use threads only, how can I handle it? Is it possible in one thread only? – Rahul Bharadwaj May 14 '18 at 04:27
  • @RahulBharadwaj the solution I have given, is using a single thread. If you want to do it using multiple threads, the right way would be to have a sleeping thread (like you have right now). After time out, it sets a `volatile` flag to 0. The main thread, sets the `stdin` (0) fd to non blocking mode and keeps trying to read (with appropriate sleep of say 10 milliseconds). Before repeating everytime it checks on the volatile variable. This way you can get the same behavior. – Ajay Brahmakshatriya May 14 '18 at 04:32
  • Ok, I understand that. I'll try to implement using one extra thread apart from `main` thread. – Rahul Bharadwaj May 14 '18 at 04:36
  • 1
    @RahulBharadwaj You can look at [this](https://stackoverflow.com/questions/5616092/non-blocking-call-for-reading-descriptor) to know how to set the 0 fd to non blocking mode. Although I would suggest, [`dup`](http://man7.org/linux/man-pages/man2/dup.2.html) the fd first and then read on that. I am not sure how the standard library code (like scanf, gets) would behave if the 0 fd is set to non blocking. – Ajay Brahmakshatriya May 14 '18 at 04:39
  • The code as show would not time out if the user does not press enter. – alk May 14 '18 at 09:09
  • @alk, I tried it on my system and it does. As the `stdin` (usually) is line buffered by the terminal, fd 0 will be available for reading only after enter is pressed. Please correct me if I am wrong. – Ajay Brahmakshatriya May 14 '18 at 09:12
1

Your friend is right and what I think he means is that you cannot assume to know which thread is run/complete first. But in 2 seconds for sure both threads will be scheduled so thread1 has the time to ask the user for input and wait a little for the input.

I don't see big problems and if it is ok then don't worry too much.

Instead of sleep however you can use SIGALRM signal but it involves masks and it takes a little more time to understand (I am doing this project: check task2 function and the mask it used if you want an example).

roschach
  • 8,390
  • 14
  • 74
  • 124
0

The pthread_cancel function calls thread cancellation handlers on the thread, which then, once they return will call the destructor of the thread, which in turn will terminate the thread.

Because it is a request, it can fail, which is indicated by the pthread_cancel return a non-zero integer.

If you are using more than two threads to do this, or more threads are accessing stdin in the same process, you might get threads waiting to access stdin, causing the desired behavior to be delayed.

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38
  • Only one thread is accessing `stdin` . Any alternatives to `pthread_cancel` you may suggest? – Rahul Bharadwaj May 14 '18 at 04:27
  • @RahulBharadwaj yes, you can use [pthread_exit](http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_exit.html), which as a return type of void and never fails. – Josh Weinstein May 14 '18 at 06:11
  • 1
    @JoshWeinstein `pthread_exit`, kills the calling thread. Thus it would have to be called from the waiting thread, which is not possible. There is no reliable way to kill a thread from another thread. – Ajay Brahmakshatriya May 14 '18 at 06:28