@@ -4,36 +4,54 @@ import (
44 "sync"
55)
66
7- // concurrencyControl controls how many concurrent operations of some type can occur at
8- // the same time. A concurrencyController is created with newConcurrencyController() which
9- // specifies the maximum number of concurrent operations. Henceforth callers call start()
10- // which will block until the number of concurrent operations is below the maximum allowed
11- // and will return when the caller can commence their concurrent operation. Once the
12- // currency operation is complete, the caller calls done() which releases up the next
13- // waiter sitting on the start() call.
7+ // concurrencyController controls how many gorutines can run at the same time.
8+ //
9+ // A concurrencyController is created with newConcurrencyController() which specifies the
10+ // maximum number of concurrent operations. Henceforth callers call start() in a separate
11+ // goroutine which will block until the number of concurrent operations is below the
12+ // maximum allowed and will return when the caller can commence their concurrent
13+ // operation. Once the currency operation is complete, the caller calls done() which
14+ // releases up the next waiter sitting on the start() call.
1415//
1516// Normally the caller creates many separate goroutines, the number of which may easily
1617// exceed the concurrency limit, they call start() and are concurrently enabled upto the
17- // configured maximum.
18+ // configured maximum. In effect, concurrencyControl uses the goroutine stack as the queue
19+ // of pending goroutines to run.
20+ //
21+ // Idiomatic code looks like this:
22+ //
23+ // const max = 10
24+ // const required = 20
25+ // cc := newConcurrencyController(max)
26+ // for ix := 0; ix < required; ix++ {
27+ // go func() {
28+ // cc.start() // Stalls if max exceeded
29+ // doStuff()
30+ // cc.done()
31+ // }()
32+ // }
33+ // cc.wait() // All functions are complete on return
1834//
1935// Very limit checking is done to ensure that callers are honoring the correct call
20- // sequence and inappropriate calls, such as more done() calls than start() calls can
21- // result in a panic or a deadlock or some other undesired behaviour.
36+ // sequence and avoiding inappropriate calls; e.g.: making more done() calls than start()
37+ // calls is not checked, but it can result in a panic or a deadlock or some other
38+ // undesired behaviour.
2239//
2340// The control mechanism is a channel containing the concurrency limit of tokens. A call
2441// to start() reads the next token from the channel and a call to done() writes a
2542// previously read token back to the channel. The caller is responsible for ensuring
2643// matching calls to start() and done().
27-
28- type concurrencyToken struct {}
29-
3044type concurrencyController struct {
3145 sync.RWMutex
3246 limit int
3347 minimum int
3448 tokens chan concurrencyToken
3549}
3650
51+ type concurrencyToken struct {}
52+
53+ // newConcurrencyController creates a concurrencyController which allows a maximum of
54+ // "limit" concurrency goroutines to run at any one time.
3755func newConcurrencyController (limit int ) * concurrencyController {
3856 cc := & concurrencyController {limit : limit , minimum : limit ,
3957 tokens : make (chan concurrencyToken , limit )}
@@ -45,6 +63,9 @@ func newConcurrencyController(limit int) *concurrencyController {
4563 return cc
4664}
4765
66+ // start is normally called by an independent goroutine. start blocks until the number of
67+ // running goroutines is below the limit at which point it returns to the caller. A caller
68+ // calls done() on completion to allow the next goroutine blocked on start() to proceed.
4869func (cc * concurrencyController ) start () {
4970 cc .Lock ()
5071 defer cc .Unlock ()
@@ -56,11 +77,13 @@ func (cc *concurrencyController) start() {
5677 }
5778}
5879
80+ // done informs the concurrencyController that this goroutine has completed and that the
81+ // next goroutine blocked on start() can proceed.
5982func (cc * concurrencyController ) done () {
6083 cc .tokens <- concurrencyToken {}
6184}
6285
63- // Return true if all tokens have been issued at any time since creation
86+ // limitReached returns true if all tokens have been issued at any time since creation
6487func (cc * concurrencyController ) limitReached () bool {
6588 cc .RLock ()
6689 defer cc .RUnlock ()
0 commit comments