mirror of https://github.com/golang/go.git
doc: allow buffered channel as semaphore without initialization
This rule not existing has been the source of many discussions on golang-dev and on issues. We have stated publicly that it is true, but we have never written it down. Write it down. Fixes #6242. LGTM=r, dan.kortschak, iant, dvyukov R=golang-codereviews, r, dominik.honnef, dvyukov, dan.kortschak, iant, 0xjnml CC=golang-codereviews https://golang.org/cl/75130045
This commit is contained in:
parent
833dae6d26
commit
132e816734
|
|
@ -2942,26 +2942,19 @@ means waiting until some receiver has retrieved a value.
|
|||
<p>
|
||||
A buffered channel can be used like a semaphore, for instance to
|
||||
limit throughput. In this example, incoming requests are passed
|
||||
to <code>handle</code>, 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.
|
||||
to <code>handle</code>, which sends a value into the channel, processes
|
||||
the request, and then receives a value from the channel
|
||||
to ready the “semaphore” for the next consumer.
|
||||
The capacity of the channel buffer limits the number of
|
||||
simultaneous calls to <code>process</code>,
|
||||
so during initialization we prime the channel by filling it to capacity.
|
||||
simultaneous calls to <code>process</code>.
|
||||
</p>
|
||||
<pre>
|
||||
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
|
||||
}
|
||||
sem <- 1 // Wait for active queue to drain.
|
||||
process(r) // May take a long time.
|
||||
<-sem // Done; enable next request to run.
|
||||
}
|
||||
|
||||
func Serve(queue chan *Request) {
|
||||
|
|
@ -2973,10 +2966,9 @@ func Serve(queue chan *Request) {
|
|||
</pre>
|
||||
|
||||
<p>
|
||||
Because data synchronization occurs on a receive from a channel
|
||||
(that is, the send "happens before" the receive; see
|
||||
<a href="/ref/mem">The Go Memory Model</a>),
|
||||
acquisition of the semaphore must be on a channel receive, not a send.
|
||||
Once <code>MaxOutstanding</code> handlers are executing <code>process</code>,
|
||||
any more will block trying to send into the filled channel buffer,
|
||||
until one of the existing handlers finishes and receives from the buffer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
@ -2993,10 +2985,10 @@ Here's an obvious solution, but beware it has a bug we'll fix subsequently:
|
|||
<pre>
|
||||
func Serve(queue chan *Request) {
|
||||
for req := range queue {
|
||||
<-sem
|
||||
sem <- 1
|
||||
go func() {
|
||||
process(req) // Buggy; see explanation below.
|
||||
sem <- 1
|
||||
<-sem
|
||||
}()
|
||||
}
|
||||
}</pre>
|
||||
|
|
@ -3014,10 +3006,10 @@ to the closure in the goroutine:
|
|||
<pre>
|
||||
func Serve(queue chan *Request) {
|
||||
for req := range queue {
|
||||
<-sem
|
||||
sem <- 1
|
||||
go func(req *Request) {
|
||||
process(req)
|
||||
sem <- 1
|
||||
<-sem
|
||||
}(req)
|
||||
}
|
||||
}</pre>
|
||||
|
|
@ -3032,11 +3024,11 @@ name, as in this example:
|
|||
<pre>
|
||||
func Serve(queue chan *Request) {
|
||||
for req := range queue {
|
||||
<-sem
|
||||
req := req // Create new instance of req for the goroutine.
|
||||
sem <- 1
|
||||
go func() {
|
||||
process(req)
|
||||
sem <- 1
|
||||
<-sem
|
||||
}()
|
||||
}
|
||||
}</pre>
|
||||
|
|
|
|||
|
|
@ -274,6 +274,41 @@ then the program would not be guaranteed to print
|
|||
crash, or do something else.)
|
||||
</p>
|
||||
|
||||
<p class="rule">
|
||||
The <i>k</i>th send on a channel with capacity <i>C</i> happens before the <i>k</i>+<i>C</i>th receive from that channel completes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This rule generalizes the previous rule to buffered channels.
|
||||
It allows a counting semaphore to be modeled by a buffered channel:
|
||||
the number of items in the channel corresponds to the semaphore count,
|
||||
the capacity of the channel corresponds to the semaphore maximum,
|
||||
sending an item acquires the semaphore, and receiving an item releases
|
||||
the semaphore.
|
||||
This is a common idiom for rate-limiting work.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This program starts a goroutine for every entry in the work list, but the
|
||||
goroutines coordinate using the <code>limit</code> channel to ensure
|
||||
that at most three are running work functions at a time.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
var limit = make(chan int, 3)
|
||||
|
||||
func main() {
|
||||
for _, w := range work {
|
||||
go func() {
|
||||
limit <- 1
|
||||
w()
|
||||
<-limit
|
||||
}()
|
||||
}
|
||||
select{}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h3>Locks</h3>
|
||||
|
||||
<p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue