How to read from stdin with goroutines in Golang?

Issue

There is list of questions. I show question one after one to user and wait for user`s answer. Every question should be answered in some seconds( for example 5 second for question). If question is answered right and in time, then user get some points.
My code is looks like:

 for i := 0; i < len(questions); i++ {
        fmt.Println(questions[i].Text)
        ans := make(chan int)
        go func() {
            fmt.Print("Enter answer ")
            var u int
            fmt.Scanf("%d\n", &u)
            ans <- u
        }()

        select {
        case userAnswer := <-ans:
            if userAnswer == questions[i].Answer {
                points++
            }
        case <-time.After(5 * time.Second):
            fmt.Println("\n Time is over!")
        }
    }

Problem is next: if user don`t answer to question, then he get message “Time is over”, as expected. But next answer will not be processed, and user should type it again. It looks like next output:

question with answer  1
Enter answer: 1
1  is right answer
question with answer  2
Enter answer: 2
2  is right answer
question with answer  3
Enter answer: 
 Time is over!
question with answer  4
Enter answer: 4
4
4  is right answer
question with answer  5
Enter answer: 5
5  is right answer

User did not answer to question #3, so he need to answer to question #4 twice.
I understand that problem is because goroutines and channels. But I don`t understand, why was not value, that was read from stdin after timeout, sent to or get from channel “ans”.

Why value from channel was not properly received after timeout? How I can rewrite code, so user don`t need to repeat input twice after timeout to previous question?

Sorry for bad english and thanks for help.

Solution

What’s going on here is that when you time out you still have a fmt.Scanf going in the previous goroutine. You’re also allocating a new channel each loop. The end result means the scan from question 3 gets your first input of 4 and then tries to push it onto a channel that will never be read from. The second time you enter 4 it is read by the new goroutine and is then pushed onto the channel you’re expecting to find the user’s input on.

I’d suggest, instead, that you offload the user input into a single goroutine that feeds a single channel.

func readInput(input chan<- int) {
    for {
        var u int
        _, err := fmt.Scanf("%d\n", &u)
        if err != nil {
            panic(err)
        }
        input <- u
    }
}

And then process your questions like so:

func main() {
    var points int
    userInput := make(chan int)

    go readInput(userInput)

    for i := 0; i < len(questions); i++ {
        fmt.Println(questions[i].Text)
        fmt.Print("Enter answer ")

        select {
        case userAnswer := <-userInput:
            if userAnswer == questions[i].Answer {
                fmt.Println("Correct answer:", userAnswer)
                points++
            } else {
                fmt.Println("Wrong answer")
            }
        case <-time.After(5 * time.Second):
            fmt.Println("\n Time is over!")
        }
    }
}

You would probably want to add some additional logic or handling to terminate the input reading goroutine at some point, depending on the actual life cycle of your program but that’s a different problem.

Answered By – Thomas

Answer Checked By – Jay B. (GoLangFix Admin)

Leave a Reply

Your email address will not be published.