Golang设置10秒超时的方法与问题排查

Golang设置10秒超时的方法与问题排查 你好,我正在编写一个程序,程序会询问用户“是否要继续?”。如果用户输入“是”,则程序继续运行;如果用户在10秒内没有输入任何内容,则程序将退出。我们使用Scanf读取输入,是否有办法实现这个功能?

7 回复

上述代码在 answer 变量上创建了一个竞态条件,因为一个协程在 fmt.Scanln(..) 处写入 answer,而另一个协程在 if answer == "" { .. } 处读取 answer

更多关于Golang设置10秒超时的方法与问题排查的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


哟, 你需要使用 go-func 语句。 它会为你的函数启动一个新的 goroutine。就像这样:

        var answer string = ""
        go func() {
                time.Sleep(10 * time.Second)
                if answer == "" {
                        fmt.Println("timeout")
                        os.Exit(0)
                }
        }()

        fmt.Scanln(&answer)
        fmt.Println("answer is " + answer)

或许可以像这样:

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	result := make(chan string)

	go func() {
		var answer string

		fmt.Scanln(&answer)

		result <- answer
	}()

	select {
	case r := <-result:
		fmt.Println("answer is:", r)
	case <-ctx.Done():
		fmt.Println("timeout")
	}
}

这不就是 <-time.After(duration) 的用途吗?

package main

import (
	"fmt"
	"time"
)

func main() {
	result := make(chan string)

	go func() {
		var answer string
		fmt.Scanln(&answer)
		result <- answer
	}()

	select {
	case <-time.After(10 * time.Second):
		fmt.Println("timeout")
	case r := <-result:
		fmt.Println("answer is:", r)
	}
}

我没有发现 go run -race 返回任何问题,但如果有的话,我们或许可以在这里使用互斥锁,所以:

package main

import (
	"fmt"
	"os"
	"sync"
	"time"
)

func main() {
	m := &sync.Mutex{}
	var answer string = ""
	fmt.Print("do you want to continue?: ")
	go func() {
		time.Sleep(10 * time.Second)
		m.Lock()
		isEmpty := answer == ""
		m.Unlock()
		if isEmpty {
			fmt.Println("timeout")
			os.Exit(0)
		}
	}()

	fmt.Scanln(&answer)
	fmt.Println("answer is " + answer)
}

互斥锁也必须包裹住 fmt.Scanln(&answer) —— 但这并不是一个好主意,因为 fmt.Scanln 会阻塞更长时间,从而也会阻塞互斥锁。


除此之外,没有办法取消 fmt.Scanln 调用,因为它从 os.Stdin 读取。所以,即使你得到了超时(例如在另一个 goroutine 中使用 time.Sleep),也没有函数可以停止 fmt.Scanln

为了解决这个问题,不要直接在 os.Stdin 上使用 Scanln,而是以一种更非阻塞的方式从 os.Stdin 读取:

package main

import (
	"fmt"
	"time"
	"bufio"
	"os"
)

func main() {
	stdin := make(chan string)

	go func() {
		reader := bufio.NewReader(os.Stdin)
		// read Stdin line by line
		for {
			line, _ := reader.ReadString('\n')
			stdin <- line
		}
	}()

	select {
	case line := <-stdin:
		fmt.Println("Your input:", line)
		// use fmt.Scanf(line, ...) now

	case <-time.After(10 * time.Second):
		fmt.Println("Timeout.")
	}
}

在Go中实现带超时的用户输入,可以使用time.After配合select语句。以下是示例代码:

package main

import (
    "bufio"
    "fmt"
    "os"
    "time"
)

func main() {
    fmt.Print("是否要继续?(10秒内输入'是'继续): ")
    
    // 创建带缓冲的读取器
    reader := bufio.NewReader(os.Stdin)
    inputChan := make(chan string)
    
    // 启动goroutine读取输入
    go func() {
        input, _ := reader.ReadString('\n')
        inputChan <- input
    }()
    
    // 设置10秒超时
    select {
    case input := <-inputChan:
        if input == "是\n" {
            fmt.Println("程序继续运行")
            // 继续执行后续代码
        } else {
            fmt.Println("输入不正确,程序退出")
        }
    case <-time.After(10 * time.Second):
        fmt.Println("\n10秒内无输入,程序退出")
    }
}

如果遇到输入阻塞问题,可以改用bufio.NewReader替代Scanf,因为Scanf在等待完整输入时无法响应超时。以下是更健壮的版本:

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "time"
)

func main() {
    fmt.Print("是否要继续?(10秒内输入'是'继续): ")
    
    reader := bufio.NewReader(os.Stdin)
    inputChan := make(chan string, 1)
    
    go func() {
        text, err := reader.ReadString('\n')
        if err != nil {
            inputChan <- ""
            return
        }
        inputChan <- strings.TrimSpace(text)
    }()
    
    select {
    case input := <-inputChan:
        if input == "是" {
            fmt.Println("程序继续运行")
        } else {
            fmt.Println("程序退出")
        }
    case <-time.After(10 * time.Second):
        fmt.Println("\n超时,程序退出")
    }
}

对于Windows系统,需要处理回车换行符差异:

func readInput(reader *bufio.Reader) string {
    text, _ := reader.ReadString('\n')
    // 跨平台换行符处理
    text = strings.ReplaceAll(text, "\r\n", "")
    text = strings.ReplaceAll(text, "\n", "")
    return text
}

如果程序在超时后需要清理资源,可以添加defer语句:

defer func() {
    fmt.Println("执行清理操作...")
}()
回到顶部