New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: sync: Add sync.Semaphore #19120
Comments
I think the canonical buffered channel based semaphore uses send to
release, rather than acquire.
I.e.
sem := make(chan struct{}, N)
for i := 0; i < N; i++ { sem <- struct{}{} } // fill with tickets
acquire: <-sem
release: sem <- struct{}{}
It's guaranteed that send won't block so goroutine can always release the
semaphore without blocking.
Also, this also stays within the memory model specification (the happen
before edge is only from send to receive, so strictly speaking the other
implementation is wrong w.r.t. the memory model.)
|
That's interesting. I tried implementing it the way you mention, and using send to release seems to work. However, I'm surprised that it is the canonical implementation, because Effective Go, the wiki, and the majority of the semaphore implementations on github seem to use send for acquire. Either I'm understanding something incorrectly, or there are many incorrect implementations out there. In this example from Effective Go, if another goroutine received from sem before process() had been finished, and the channel was empty, why wouldn't the receive after process() block? func handle(r *Request) {
sem <- 1 // Wait for active queue to drain.
process(r) // May take a long time.
// another goroutine could receive somewhere here right?
<-sem // Done; enable next request to run.
// empty channel receive can block
} Regardless, perhaps the documentation needs to be updated? Also, being a non-communicating use of the channel, wouldn't it be preferable to have a standard implementation that allows for more structures to be built on top of it? |
The receive (B) will never block (C) in this scenario because before the receive a send must have happened (A) and receive from channel blocks only when the channel is empty. IOW, the proposal has the same semantics as the current state of art. |
Sorry, I must have misunderstood channel semantics. Release (either in send or receive) cannot block because all goroutines that try to access that data must have acquired. |
A common technique used to limit the amount of concurrency in a program or access to a resource is to use a buffered channel of an empty struct.
Many Go programmers, including myself, try to use the Send and Receive operations on a buffered channel like the Increment and Decrement operations on a semaphore. However, the added semantics of blocking when receiving from channel prevents the termination of a goroutine until another send. Additionally, checking the current length of a channel can result in stale data, as discussed here.
The difference:
Additionally,
sync.WaitGroup
is often suggested as a way to control the number of concurrent operations, but it's more of a rendezvous point with a counter, rather than reusable guard.A cursory search on github shows thousands of slightly varied implementations of the traditional counting semaphore. Some use the incomplete model of the buffered channel, while others use
sync.RWMutex
orsync.Cond
to synchronize modification to the counter.Seeing that the runtime already makes heavy use of semaphores in /runtime/sema.go, is there value in an official and efficient version for a public API?
The text was updated successfully, but these errors were encountered: