Skip to content
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

Closed
smasher164 opened this issue Feb 16, 2017 · 4 comments
Closed

Proposal: sync: Add sync.Semaphore #19120

smasher164 opened this issue Feb 16, 2017 · 4 comments

Comments

@smasher164
Copy link
Member

smasher164 commented Feb 16, 2017

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.

sem := make(struct{}, 4)

// Acquire
sem <- struct{}{}

// Release
<-sem

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:

1. Regular Semaphore
        Decrement until < 0 (until a currently using thread increments)
                Can do operations now
	Increment to let another thread get the resource (decrement and do its operations)

2. Buffered Channel
        Send blocks until space is available (until someone receives)
		can do operations now
	Receive to designate someone else the resource (Send and do their operations)
        (blocking operation!)

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 or sync.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?

@dsnet dsnet added the Proposal label Feb 16, 2017
@dsnet dsnet added this to the Proposal milestone Feb 16, 2017
@minux
Copy link
Member

minux commented Feb 16, 2017 via email

@smasher164
Copy link
Member Author

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?

@cznic
Copy link
Contributor

cznic commented Feb 16, 2017

Buffered Channel
Send blocks until space is available (until someone receives) (A)
can do operations now
Receive to designate someone else the resource (Send and do their operations) (B)
(blocking operation!) (C)

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.

@smasher164
Copy link
Member Author

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.

@golang golang locked and limited conversation to collaborators Feb 16, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants