Effective Go дает следующий пример того, как имитировать семафор с каналами:
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)
}
}
В нем также говорится: Поскольку синхронизация данных происходит при получении из канала (то есть отправка «происходит до» получения; см. Go Memory Model), получение семафора должно происходить на приеме канала, а не на отправке.
Теперь, я думаю, я понимаю модель памяти Go и определение «произошло раньше». Но я не вижу, в чем проблема с блокировкой отправки канала:
func handle(r *Request) {
sem <- 1
process(r)
<-sem
}
func init() {}
Этот код (с неизменными sem
и Serve
сверху) использует буферизованный канал противоположным образом. Канал будет пустым. При вводе handle
отправка будет заблокирована, если уже есть MaxOutstanding
горутины, выполняющие этот процесс. Как только один из них завершит свою обработку и «освободит» слот из канала, получив один int, наша отправка будет разблокирована, и горутина начнет свою собственную обработку.
Почему это плохой способ синхронизации, как, кажется, подразумевается в учебнике?
Произойдет ли операция приема, освобождающая слот канала, не «до» отправки, которая будет использовать тот же слот? Как это возможно?
Другими словами, в справочнике по языку говорится, что "отправка по буферизированному каналу [блокируется до тех пор, пока] не останется места в буфере. "
Но в модели памяти только сказано, что «получение из небуферизованного канала происходит до того, как отправка по этому каналу завершится». / em> В частности, он не говорит, что получение из буферизованного канала, который заполнен, происходит до завершения отправки по этому каналу.
Это какой-то угловой случай, которому нельзя доверять? (который на самом деле будет синхронизировать отправку, которая была заблокирована, с получением, которое ее разблокирует)
Если это так, это выглядит как неприятное состояние гонки на языке, разработанном для минимизации скрытых условий гонки :-(
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)
}