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

时间:2017-12-01 03:53:39

标签: go timeout

我知道有一个名为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 
} 

这个问题与post类似,但答案尚不清楚。 你们有什么好主意来解决这个问题吗?

3 个答案:

答案 0 :(得分: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()方法内的超时也无济于事,因为该超时适用于每个调用而不适用于整个文件。

答案 1 :(得分:0)

除了@apxp关于循环遍历Read的要点外,您还可以使用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盲目地重试被阻止的Reads,就可以了。

答案 2 :(得分:0)

您可以在超时后read close而不是直接在os.File上设置超时。如https://golang.org/pkg/os/#File.Close

中所述
  

Close关闭文件,使其无法用于I / O。在支持SetDeadline的文件上,所有挂起的I / O操作都将被取消并立即返回错误。

这会导致您的read立即失败。