문제

Effective Go gives this example on how to emulate a semaphore with channels:

var sem = make(chan int, MaxOutstanding)

func handle(r *Request) {
    <-sem
    process(r)
    sem <- 1
}

func init() {
    for i := 0; i < MaxOutstanding; i++ {
        sem <- 1
    }
}

func Serve(queue chan *Request) {
    for {
        req := <-queue
        go handle(req)
    }
}

It also says: 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.

Now, I think I understand the Go Memory Model and the definition of "happens before." But I fail to see what's the problem with blocking on a channel send:

func handle(r *Request) {
    sem <- 1
    process(r)
    <-sem
}

func init() {}

This code (with sem and Serve unchanged from above) uses the buffered channel in the opposite way. The channel starts empty. On entering handle, the send will block if there are already MaxOutstanding goroutines doing the process. As soon as one of them finishes its processing and "frees" a slot from the channel, by receiving one int, our send will be unblocked and the goroutine will start its own processing.

Why is this a bad way to do synchronization, as the textbook seems to imply?

Does a receive operation that frees a channel slot not "happen before" the send that will use that same slot? How is this possible?


In other words, the Language Reference says that "a send on a buffered channel [blocks until] there is room in the buffer."

But the Memory Model only says that "A receive from an unbuffered channel happens before the send on that channel completes." In particular, it does not say that a receive from a buffered channel that is full happens before a send on that channel completes.

Is this some corner case that can not be trusted to do the Right Thing? (which would be actually synchronizing a send that was blocked with the receive that unblocks it)

If that's the case, it looks like a nasty race condition in a language designed to minimize sneaky race conditions :-(

var c = make(chan int, 1)
var a string

func f() {
    a = "hello, world"
    <-c  // unblock main, which will hopefully see the updated 'a'
}

func main() {
    c <- 0  // fill up the buffered channel
    go f()
    c <- 0  // this blocks because the channel is full
    print(a)
}
도움이 되었습니까?

해결책

This bit of the Effective Go document threw me also. In fact, in relatively recent versions of Effective Go, the code in question acquired the semaphore on a channel send (instead of a channel receive like it does in the current version, which uses the init() to "prime" the channel).

There has apparently been a good deal of discussion on the topic. I won't bother trying to summarize everything, but the discussion can all be found from here:

https://code.google.com/p/go/issues/detail?id=5023

It does strike me as unfortunate, but quoting the filer of that issue, the short story appears to be that unless the semaphore is acquired on the channel receive...:

The following code:

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    process(r)  // May take a long time.
    <-sem       // Done; enable next request to run.
}

...could legally be "optimized" into:

func handle(r *Request) {
    process(r)  // May take a long time.
    sem <- 1    // Wait for active queue to drain.
    <-sem       // Done; enable next request to run.
}

...or into:

func handle(r *Request) {
    sem <- 1    // Wait for active queue to drain.
    <-sem       // Done; enable next request to run.
    process(r)  // May take a long time.
}

다른 팁

If I understand it right (which it's likely that I don't) the problem is just that the language doesn't have the right guarantees for which order some of these things will happen for it to be used that way.

When I have run into something like this I've usually figured out (sometimes after embarrassingly much trial and error) that it wasn't that the language was "missing something" but that I was trying to paint with a hammer.

In the specific example you have on top I'd solve it by structuring it a little differently:

Instead of having the semaphore in the sender (and unblock in the receiver) just spawn the desired number of goroutines up front and then send them work over a channel. No semaphores needed. I understand this was just a condensed example, but if you describe your actual use case/issues in more detail it's likely someone will chime in with a clean go-like solution for it.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top