Отдать предпочтение одному общению (чан) в избранном

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

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

Вот упрощенная версия кода:

   ctx, cancel := context.WithCancel(context.Background())
   ch := make(chan int)

   go func() {
      defer close(ch)
      for i := 1; ; i++ {
         select {
         case <-ctx.Done():
            return
         case ch <- i:
         }
      }
   }()

   print(<-ch)
   print(<-ch)
   cancel()
   print(<-ch)
   print(<-ch)

Иногда выводится 1200, но обычно 1230. Попробуйте это на игровой площадке

Есть ли идеи, как реорганизовать код в пользу первого случая? (Т.е. он всегда должен печатать 1200.)


person AJR    schedule 31.07.2020    source источник
comment
Это довольно необычный код. Обычно вы не получаете от ch после отмены контекста. И в любом случае у вас нет причин ожидать, что здесь будут напечатаны какие-либо конкретные значения. Я думаю, это не очень полезное приближение к реальному коду.   -  person Peter    schedule 01.08.2020
comment
@Peter, это чем-то напоминает ситуацию с Timer.Reset()   -  person Burak Serdar    schedule 01.08.2020
comment
@BurakSerdar Reset следует вызывать только для остановленных или истекших таймеров с истощенными каналами. от времени. Таймер. Сброс документов.   -  person leaf bebop    schedule 01.08.2020
comment
@ Питер, я считаю, что это на самом деле хороший пример. настоящего кода. На самом деле вы в настоящее время должны продолжать чтение из chan после вызова cancel(), иначе горутина, записывающая в него, может заблокироваться и никогда не завершиться. Кстати, я бы предпочел не читать чан после отмены, и в этом весь смысл этого вопроса.   -  person AJR    schedule 03.08.2020
comment
Нет, горутина не будет блокироваться, потому что становится доступным кейс ctx.Done ().   -  person Peter    schedule 03.08.2020
comment
Я предполагаю, что select предпочитает регистр с записью в канал, потому что основная goroutine читает из него. Вы можете добавить еще один выбор перед тем, что в примере, чтобы убедиться, что сначала обрабатывается контекст. play.golang.org/ p / H1U1D8GpBuT   -  person D.C. Joo    schedule 04.08.2020
comment
@Peter сказал the goroutine will not block ... Спасибо @Peter, вы правы. Больше всего меня беспокоила утечка горутины, но теперь я вижу, что это невозможно, поэтому я думаю, что решением является никаких изменений в моем исходном коде.   -  person AJR    schedule 24.08.2020


Ответы (2)


Это не представляется возможным, потому что cancel() не является блокирующей операцией в основной горутине. Из-за этого, когда select разблокируется, может быть доступно несколько случаев, и нет возможности отдать предпочтение одному каналу над другим. Любая схема проверки канала и записи будет полезной, потому что контекст может быть отменен после проверки.

Использование done канала и запись в него вместо отмены контекста будут работать, потому что запись в done канал будет блокирующей операцией для основной горутины, а select всегда будет иметь один активный случай.

person Burak Serdar    schedule 01.08.2020
comment
Спасибо, Бурак, я вроде уже понял ваш первый абзац, поэтому я просил поделиться идеями о том, как реорганизовать код. Я попробую сделать chan, но я надеялся, что есть способ использовать context. - person AJR; 03.08.2020
comment
Я начал писать ответ на этот вопрос как «Вот как вы это делаете ...», который позже превратился в то, что мы изложили выше. По мере того, как я думаю об этом больше, я убежден, что не может быть способа сделать это с помощью отмены контекста из-за того, как работает select, и потому что эффекты cancel() не сразу известны горутине (т.е. cancel() не пробуждает горутину) - person Burak Serdar; 03.08.2020

Обратите внимание, что это обновленный ответ, так как с оригиналом были проблемы.

Как указывалось другими, вы не можете избежать состояния гонки без дополнительной синхронизации. Вы можете использовать Mutex, но sync.Cond кажется вам подходящим. В коде ниже принимающая горутина сигнализирует о том, что она получила значение от чана. Он отменяет контекст перед сигнализацией (используя Cond.Signal), и отправляющая горутина ожидает сигнала. Это позволяет избежать состояния гонки, поскольку статус контекста обновляется до того, как его можно будет проверить.

ctx, cancel := context.WithCancel(context.Background())
ch := make(chan int)
cond := sync.NewCond(&sync.Mutex{}) // *** new

go func() {
    defer close(ch)
    cond.L.Lock()                   // *** new
    defer cond.L.Unlock()           // *** new
    for i := 1; ; i++ {
        ch <- i                     // *** moved
        cond.Wait()                 // *** new
        if ctx.Err() != nil {       // *** changed
            return
        }
    }
}()

print(<-ch)
cond.Signal()                      // *** new
print(<-ch)
cond.Signal()                      // *** new
print(<-ch)
cancel()
cond.Signal()                      // *** new
print(<-ch)
cond.Signal()                      // *** new

Это самый простой способ увидеть, что принимающая горутина больше не будет получать значения в канале после того, как она отменила контекст.

Попробуйте это на игровой площадке

person Andrew W. Phillips    schedule 01.08.2020
comment
Это не сработает, контекст можно отменить во время ожидания записи в канал. - person Burak Serdar; 01.08.2020
comment
Это лучше, поскольку обычно печатается 1200, но, как говорит Бурак, это не решение - например: добавить сон перед отменой play.golang.org/p/T6fEX2ZGCHJ - person AJR; 01.08.2020
comment
Предоставленный ответ был помечен для просмотра как сообщение низкого качества. Вот несколько рекомендаций для Как написать хороший ответ?. Этот предоставленный ответ может быть правильным, но для него может быть полезно объяснение. Ответы только на код не считаются хорошими ответами. - person Trenton McKinney; 01.08.2020
comment
Спасибо за комментарии. Я добавил объяснение и исправил код для достижения того, что пытался сделать OP. - person Andrew W. Phillips; 24.08.2020