Отдать предпочтение одному общению (чан) в избранном - PullRequest
1 голос
/ 01 августа 2020

Я знаю, что если существует более одного «сообщения», которое может происходить в операторе 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. Попробуйте на игровой площадке

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

Ответы [ 2 ]

1 голос
/ 01 августа 2020

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

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

0 голосов
/ 01 августа 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

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

Попробуйте на Playground

...