Do not communicate by sharing memory; instead, share memory by communicating.
c := make(chan int) // Allocate a channel. // Start the sort in a goroutine; when it completes, signal on the channel. go func() { list.Sort() c <- 1 // Send a signal; value does not matter. }() doSomethingForAWhile() <-c // Wait for sort to finish; discard sent value.
Receivers always block until there is data to receive. If the channel is unbuffered, the sender blocks until the receiver has received the value. If the channel has a buffer, the sender blocks only until the value has been copied to the buffer; if the buffer is full, this means waiting until some receiver has retrieved a value.
接受者总会阻塞,直到收到data。如果channel是带缓存的,那么发送者就不会阻塞,除非缓存被占满,这就意味着直到缓存中的数据被接受者读取才能停止阻塞。
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. } }
A buffered channel can be used like a semaphore, for instance to limit throughput. In this example, incoming requests are passed to handle
, which receives a value from the channel, processes the request, and then sends a value back to the channel to ready the "semaphore" for the next consumer. The capacity of the channel buffer limits the number of simultaneous calls to process
, so during initialization we prime the channel by filling it to capacity.
一个缓冲channel,可以用来作为信号,比如控制吞吐量。在这个例子里面,每个请求都发送给handle,handle都从channel中提取一个值,然后再进行相应处理,最后把值传回channel,这就好像有一堆工作牌,每个员工要在工作之前拿工作牌,然后再工作完之后把工作牌换回去。在init里面事前放置好了一堆工作牌,但是handle最多只能拿那么多工作牌。这就是说能进行的goroutine就可以控制在少于MaxOutstanding这个数量内,因为只有那么多工作牌可以拿。
Because data synchronization occurs on a receive from a channel (that is, the send "happens before" the receive; see The Go Memory Model), acquisition of the semaphore must be on a channel receive, not a send.
因为所有的同步都发生在channel的接收方,所以信号的获得必须在channel的接收方而不是发送方。
This design has a problem, though: Serve
creates a new goroutine for every incoming request, even though only MaxOutstanding
of them can run at any moment. As a result, the program can consume unlimited resources if the requests come in too fast. We can address that deficiency by changing Serve
to gate the creation of the goroutines. Here's an obvious solution, but beware it has a bug we'll fix subsequently:
虽然前面开 MaxOutstanding 个goroutine会堵塞,但是不会阻止开goroutine,会毫不犹豫地开无数个goroutine。下面那个在开goroutine之前就开始堵塞所以还能限制goroutine的数量。
func Serve(queue chan *Request) { for req := range queue { <-sem go func() { process(req) // Buggy; see explanation below. sem <- 1 }() } }
Bug就是在迭代里面,循环变量是重复利用的,所有的gorountine用的都是一个req,要么传值调用函数,作一个闭包,要么在循环中暂时保留这个变量。一下是两种改进方法
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
but it's a legal and idiomatic in Go to do this. You get a fresh version of the variable with the same name, deliberately shadowing the loop variable locally but unique to each goroutine.
可以用来更新变量别且为每个goroutine保存一个唯一的req
func handle(queue chan *Request) { for r := range queue { process(r) } } func Serve(clientRequests chan *Request, quit chan bool) { // Start handlers for i := 0; i < MaxOutstanding; i++ { go handle(clientRequests) } <-quit // Wait to be told to exit. }
Going back to the general problem of writing the server, another approach that manages resources well is to start a fixed number of handle
goroutines all reading from the request channel. The number of goroutines limits the number of simultaneous calls to process
. This Serve
function also accepts a channel on which it will be told to exit; after launching the goroutines it blocks receiving from that channel.
这个就是开maxoutstanding数量的goroutine,但是每个routine都从channel里面拿req。Server会堵塞,直到接到退出的信号,这样的话,就不会再goroutine还没有跑完的情况下,主线程先结束了。
channel的channel
type Request struct { args []int f func([]int) int resultChan chan int }
客户端提供一个func和参数,还有一个channel去接收答案
func sum(a []int) (s int) { for _, v := range a { s += v } return } request := &Request{[]int{3, 4, 5}, sum, make(chan int)} // Send request clientRequests <- request // Wait for response. fmt.Printf("answer: %d\n", <-request.resultChan)
func handle(queue chan *Request) { for req := range queue { req.resultChan <- req.f(req.args) } }
服务端唯一变得是handle
}
There's clearly a lot more to do to make it realistic, but this code is a framework for a rate-limited, parallel, non-blocking RPC system, and there's not a mutex in sight.
需要更多code实现,但是这是一个RFC框架
还有一点就是用来并行
type Vector []float64 // Apply the operation to v[i], v[i+1] ... up to v[n-1]. func (v Vector) DoSome(i, n int, u Vector, c chan int) { for ; i < n; i++ { v[i] += u.Op(v[i]) } c <- 1 // signal that this piece is done }
const NCPU = 4 // number of CPU cores func (v Vector) DoAll(u Vector) { c := make(chan int, NCPU) // Buffering optional but sensible. for i := 0; i < NCPU; i++ { go v.DoSome(i*len(v)/NCPU, (i+1)*len(v)/NCPU, u, c) } // Drain the channel. for i := 0; i < NCPU; i++ { <-c // wait for one task to complete } // All done. }
把vector分成4分,每个CPU干一份。
The current implementation of the Go runtime will not parallelize this code by default. It dedicates only a single core to user-level processing. An arbitrary number of goroutines can be blocked in system calls, but by default only one can be executing user-level code at any time. It should be smarter and one day it will be smarter, but until it is if you want CPU parallelism you must tell the run-time how many goroutines you want executing code simultaneously. There are two related ways to do this. Either run your job with environment variable GOMAXPROCS
set to the number of cores to use or import the runtime
package and call runtime.GOMAXPROCS(NCPU)
. A helpful value might be runtime.NumCPU()
, which reports the number of logical CPUs on the local machine. Again, this requirement is expected to be retired as the scheduling and run-time improve.
Be sure not to confuse the ideas of concurrency—structuring a program as independently executing components—and parallelism—executing calculations in parallel for efficiency on multiple CPUs. Although the concurrency features of Go can make some problems easy to structure as parallel computations, Go is a concurrent language, not a parallel one, and not all parallelization problems fit Go's model.
Leaky Buffer
var freeList = make(chan *Buffer, 100) var serverChan = make(chan *Buffer) func client() { for { var b *Buffer // Grab a buffer if available; allocate if not. select { case b = <-freeList: // Got one; nothing more to do. default: // None free, so allocate a new one. b = new(Buffer) } load(b) // Read next message from the net. serverChan <- b // Send to server. } } The server loop receives each message from the client, processes it, and returns the buffer to the free list. func server() { for { b := <-serverChan // Wait for work. process(b) // Reuse buffer if there's room. select { case freeList <- b: // Buffer on free list; nothing more to do. default: // Free list full, just carry on. } } }
会有100个bufferd存在那里供使用,如果freelist满了的话,客户端会新建一个buffer,服务端则不做什么,让buffer等待被垃圾回收器回收。