Блокировка канала отправляет неверную парадигму синхронизации и почему

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

person Tobia    schedule 01.05.2013    source источник


Ответы (2)


Этот отрывок из документа Effective Go тоже меня поразил. Фактически, в относительно последних версиях Effective Go рассматриваемый код получил семафор при отправке канала (вместо приема канала, как в текущей версии, которая использует init () для «заполнения» канала).

По-видимому, по этой теме было много дискуссий. Я не буду пытаться резюмировать все, но все обсуждения можно найти здесь:

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

Мне это кажется досадным, но, цитируя автора этой проблемы, можно сказать, что если семафор не будет получен на канале приема ...:

Следующий код:

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

... можно юридически "оптимизировать" в:

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

... или в:

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.
}
person bgmerrell    schedule 02.05.2013
comment
Спасибо. Связанная с проблемой тема очень информативна. - person Tobia; 02.05.2013
comment
Насколько я могу судить, это было прояснено и исправлено начиная с Go 1.3: golang.org/doc/ go1.3 # memory и связанные с этим обсуждения, кажется, подразумевают, что этот шаблон send, do, recv является правильным ™ с буферизованными каналами. - person Groxx; 04.09.2018

Если я правильно это понимаю (а я, скорее всего, не понимаю), проблема в том, что у языка нет правильных гарантий, в каком порядке произойдут некоторые из этих вещей, чтобы он использовался таким образом.

Когда я сталкивался с чем-то подобным, я обычно приходил к выводу (иногда после оскорбительно большого количества проб и ошибок), что дело не в том, что в языке «чего-то не хватает», а в том, что я пытался рисовать молотком.

В конкретном примере, который у вас есть наверху, я бы решил его, немного по-другому структурировав его:

Вместо того, чтобы иметь семафор в отправителе (и разблокировать в получателе), просто создайте желаемое количество горутин впереди, а затем отправьте их работу по каналу. Семафоры не нужны. Я понимаю, что это был всего лишь краткий пример, но если вы опишете свой фактический вариант использования / проблемы более подробно, вероятно, кто-то подберет для него чистое решение.

person Ask Bjørn Hansen    schedule 01.05.2013
comment
Спасибо. Я бы тоже так его закодировал. Это был просто пример, взятый из Effective Go, и я пытался понять механизмы языка. - person Tobia; 02.05.2013
comment
Это хороший ответ. Семафоры по-прежнему имеют свое место, но если они приняты в качестве первого (хорошо известного) выбора, это часто обидно. Каналы и модель коммуникационного последовательного процесса (CSP) менее известны. Для этого просто нужно научиться рисовать кистью (чтобы расширить метафоры Ask) и следовать шаблону Go. Совместное использование памяти посредством общения (golang.org/doc/codewalk/sharemem). - person Rick-777; 02.05.2013
comment
@Tobia Да, Effective Go - это скорее справочник, чем поваренная книга. Я сталкивался с подобными вещами с другими их примерами, где пример может быть правильным, чтобы показать синтаксис, который они хотят показать, но он поощряет неправильные вещи. Мне действительно повезло с просмотром кода в стандартной библиотеке. Пример использования каналов для синхронизации некоторых пакетов go-metrics является иллюстративным, например github.com/rcrowley/go-metrics/blob/master/ewma.go#L72 - person Ask Bjørn Hansen; 02.05.2013