A channel lock sends the wrong synchronization paradigm and why

An efficient transition 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: since data is synchronized when it is received from the channel (that is, the transmission "occurs before" the receive, see Go memory model) ., The semaphore must be received on the channel, not on the send.

Now, I think, I understand the Go memory model and the definition of "comes earlier". But I do not see that there is a problem with blocking on the send channel:

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

This code (with sem and Serve unchanged above) uses the buffer channel in the reverse order. The channel is empty. When you handle sending will be blocked if there are already MaxOutstanding goroutines that perform this process. As soon as one of them finishes processing and "releases" the slot from the channel, having received one int, our sending will be unlocked, and goroutine will begin its own processing.

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

Does the receive operation that frees the channel slot have not “occurred before” the send that will use the same slot? How is this possible?


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

But the Memory Model only says that "Receiving from an unbuffered channel occurs before the transmission on this channel is completed." In particular, he does not say that reception from a buffered channel, which is complete , occurs before the transmission on this channel is completed.

Is this some kind of corner case that cannot be trusted to do the right thing? (which actually synchronizes the sending that was blocked by the reception that blocks it)

If this is the case, it looks like an unpleasant race condition in a language designed to minimize hidden 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) } 
+7
source share
2 answers

This bit of Effective Gov also left me. In fact, in the relatively recent versions of Effective Go, the code in question received a semaphore on the send channel (instead of the channel, as in the current version, which uses init () for the "prime" channel).

Apparently, there was a big discussion on this topic. I will not try to summarize everything, but the discussion can be found here:

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

It seems to me that this is unsuccessful, but, quoting the question about this problem, the story seems to be that if the semaphore is not received on the channel, get ...:

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. } 

... you can legally "optimize" for:

 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 in:

 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. } 
+5
source

If I understand correctly (which is probably not the case), the problem is that the language does not have the right guarantees for what order some of these things will happen to be used in this way.

When I came across something similar, I usually found out (sometimes after embarrassingly many trials and errors) that it was not that the language “missed something”, but I tried to draw with a hammer.

In the specific example that you have, I would solve it by structuring it a little differently:

Instead of having a semaphore in the sender (and unlock in the receiver), just enter the desired number of goroutines forward, and then send them through the channel. No semaphores required. I understand that this was just a concise example, but if you describe your actual use case / problem in more detail, someone will probably launch it with a clean go-like solution.

+1
source

All Articles