如何在golang中使用*os.File/io.Read设置超时

10

我知道有一个叫做SetReadDeadline的函数可以在socket(conn.net)读取时设置超时时间,而io.Read没有该功能。有一种方法是开启另一个例程作为计时器来解决这个问题,但它会带来另一个问题,即读取例程(io.Read)仍然会阻塞:

func (self *TimeoutReader) Read(buf []byte) (n int, err error) { 
    ch := make(chan bool) 
    n = 0 
    err = nil 
    go func() { // this goroutime still exist even when timeout
            n, err = self.reader.Read(buf) 
            ch <- true 
    }() 
    select { 
    case <-ch: 
            return 
    case <-time.After(self.timeout): 
            return 0, errors.New("Timeout") 
    } 
    return 
} 

这个问题类似于这篇帖子,但是答案不清楚。你们有没有好的想法来解决这个问题?


self.reader 的具体类型是什么? - Charlie Tumahai
2
对于 *os.File,请等待 Go 1.10。 另一个选项是编写一些 syscall 代码。 - Charlie Tumahai
1
你尝试过使用context这个方法吗?https://gist.github.com/dchapes/6c992bf3e943934462509338cd213e99 - Eugene Lisitsky
1
@EugeneLisitsky:无法取消已阻塞的读取。 - JimB
@JimB 很抱歉回复比较晚,举个例子,我只想在给定时间内超时或触发特定信号时关闭os.File。然而,我不知道如何退出os.File.read()。 - vinllen
显示剩余5条评论
3个回答

4

不要直接在read上设置超时时间,而是可以在超时后closeos.File。如https://golang.org/pkg/os/#File.Close所述:

关闭文件,使其无法进行I/O操作。对于支持SetDeadline的文件,任何挂起的I/O操作都将被取消并立即返回错误。

这应该会导致你的read立即失败。


1
你的错误与此不同:
当你从读取器中读取时,仅读取一次是错误的:
go func() { 
        n, err = self.reader.Read(buf) // this Read needs to be in a loop
        ch <- true 
}() 

这里有一个简单的例子(https://play.golang.org/p/2AnhrbrhLrv

buf := bytes.NewBufferString("0123456789")
r := make([]byte, 3)
n, err := buf.Read(r)
fmt.Println(string(r), n, err)
// Output: 012 3 <nil>

给定切片的大小在使用io.Reader时被使用。如果您在代码中记录n变量,您会发现并没有读取整个文件。您的goroutine外部的select语句放错了位置。
go func() {
    a := make([]byte, 1024)
    for {
        select {
        case <-quit:
            result <- []byte{}
            return
        default:
            _, err = self.reader.Read(buf)
            if err == io.EOF {
                result <- a
                return
            }
        }
    }
}()

但是还有更多!你想实现io.Reader接口。在调用Read()方法直到文件结束之后,您不应该在此处启动goroutine,因为您只读取文件的块。 另外,Read()方法内部的超时也没有帮助,因为该超时对每个调用而不是整个文件起作用。

0
除了 @apxp 关于循环读取的观点外,您还可以使用 1 字节的缓冲区大小,以便只要有数据可读,就不会阻塞。
当与外部资源交互时,任何事情都可能发生。 任何给定的 io.Reader 实现都可能简单地永远阻塞。 在这里,我将为您编写一个...
type BlockingReader struct{}

func (BlockingReader) Read(b []byte) (int, error) {
    <-make(chan struct{})
    return 0, nil
}

记住,任何人都可以实现一个接口,因此您不能做出任何假设它的行为类似于*os.File或任何其他标准库io.Reader。除了像上面那样愚蠢的编码外,一个io.Reader可能合法地连接到一个可以永远阻塞的资源。

你不能杀死gorountines,所以如果一个io.Reader真正永久阻塞,那么被阻塞的goroutine将继续消耗资源,直到应用程序终止。然而,这不应该成为问题,因为被阻塞的goroutine并不消耗太多资源,只要您不盲目地通过生成更多的gorountines重试被阻塞的读取操作即可。


使用1字节读取会大大降低性能,在一般情况下并不理想。 - Valer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接