Effective Go -> 并发
1.勿以共享方式通信,以通信实现共享。
2.与map结构类似,channel也是通过make
进行分配的
3.如果channel是无缓冲的,发送方会一直阻塞直到接收方将数据取出。如果channel带有缓冲区,发送方会一直阻塞直到数据被拷贝到缓冲区;如果缓冲区已满,则发送方只能在接收方取走数据后才能从阻塞状态恢复。
4.for在协程是的应用
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
<-sem // Wait for active queue to drain.
process(r) // May take a long time.
sem <- 1 // Done; enable next request to run.
}
func init() {
for i := 0; i < MaxOutstanding; i++ {
sem <- 1
}
}
func Serve(queue chan *Request) {
for {
req := <-queue
go handle(req) // Don't wait for handle to finish.
}
}
Serve
会为每个请求创建一个新的Goroutine,尽管在任意时刻只有最多MaxOutstanding
个可以执行。如果请求到来的速度过快,将迅速导致系统资源完全消耗。我们可以通过修改Serve
的实现来对Goroutine的创建进行限制
(上面的例子可以根据req的创建goroutine,不管sem里的值,下面是等到sem可以取值才可以创建goroutine)
func Serve(queue chan *Request) {
for req := range queue {
<-sem
go func() {
process(req) // Buggy; see explanation below.
sem <- 1
}()
}
}
BUG源自Go中for
循环的实现,循环的迭代变量会在循环中被重用,因此req
变量会在所有Goroutine间共享。这不是我们所乐见的,我们需要保证req
变量是每个Goroutine私有的。这里提供一个方法,将req
的值以参数形式提供给goroutine对应的闭包:
func Serve(queue chan *Request) { for req := range queue { <-sem go func(req *Request) { process(req) sem <- 1 }(req) } }
另一个解决方案是,干脆创建一个新的同名变量,示例如下:
func Serve(queue chan *Request) {
for req := range queue {
<-sem
req := req // Create new instance of req for the goroutine.
go func() {
process(req)
sem <- 1
}()
}
}
req := req 在Go中是一种惯用的方法。你可以如法泡制一个新的同名变量,用来为每个Goroutine创建循环变量的私有拷贝。