带缓存的 channel, 其实就是一种队列。
引入队列,通常是优化程序时希望采用的最后一种技术之一。预先添加队列可以隐藏同步问题,例如死锁,和活锁。 常见错误认识: 引入队列来尝试解决性能问题,队列几乎不会加速程序的总运行时间,它只会让程序的行为有所不同。 举例如下: 图 todo
看看如下这个pipeline: p := processRequest(done, acceptConnection(done, httpHandler)) 这个情景中可以引入队列: 结果并不是一个 stage 的运行时间已经减少了,而是它处于阻塞状态的时间减少了,这可以让这个stage 继续工作。 这个例子中,用户可能会 感受到它们的请求相应滞后, 但它们不会被拒绝服务。
问题: 队列应该放在哪里? 缓冲区大小应该是多少? 这些问题取决于管道的性质。 队列可以提高整体性能的情况 。唯一适用情况是:(满足的条件) 1。 如果一个stage 批处理 可以节省时间 2。 将输入缓冲到 比 设计为发送给(盘)更快的事物 如(存储器,内存) 的stage 例子: Go 语言的bufio 包, 下面一个示例,演示了 缓冲写入队列与 未缓冲写入的简单比较。
/**
下面的例子演示了缓冲写入队列与未缓冲写入的简单比较。
*/
func BenchmarkUnbufferedWrite(b *testing.B){
performWrite(b, tmpFileOrFatal())
}
func BenchmarkBufferedWrite(b *testing.B){
bufferedFile := bufio.NewWriter(tmpFileOrFatal())
performWrite(b, bufio.NewWriter(bufferedFile))
}
func tmpFileOrFatal() *os.File{
file, err := ioutil.TempFile("", "tmp")
if err != nil{
log.Fatalf("error: %v", err)
}
return file
}
func performWrite(b *testing.B , write io.Writer){
done := make(chan interface{})
defer close(done)
b.ResetTimer()
for bt := range take(done, repeat(done, byte(0)), b.N){
write.Write([]byte{bt.(byte)})
}
}
//BenchmarkUnbufferedWrite-8 200000 8720 ns/op
//BenchmarkBufferedWrite-8 1000000 1270 ns/op
//BenchmarkUnbufferedWrite-8 200000 8720 ns/op //BenchmarkBufferedWrite-8 1000000 1270 ns/op /** 通常 任何时候 执行操作都需要开销, 分块可能会提高系统性能。这方面的例子是打开数据库事务,就算消息校验和 以及分配连续空间。 通过在管道入口 引入队列,可以用创建请求滞后为代价 来 打破反馈循环。从调用者进入管道角度来看,请求似乎正在处理中,但需要很长时间。 排队模式: 在管道入口处。 在这个stage, 批量操作将会带来更高的效率。 队列理论/利特尔法则 todo 利特尔法则 无法预知的情况是处理请求的失败。如果由于某种原因你的管道发生混乱,你将丢失队列中所有请求。为了缓解这种情况,可以 坚持队列大小为0, 或者可以转移到一个持久队列。