我知道有一个名为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类似,但答案尚不清楚。 你们有什么好主意来解决这个问题吗?
答案 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
立即失败。